Patrón Composite
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 101 de 104.
El patrón Composite te permite tratar de manera uniforme los objetos individuales y los grupos de objetos. Compone objetos en estructuras de árbol en las que tanto los elementos individuales como los contenedores de elementos comparten la misma interfaz. Es ideal para representar jerarquías como sistemas de archivos, organigramas o componentes de interfaz de usuario.
El patrón tiene tres partes clave: una interfaz Componente que define operaciones comunes, clases Hoja que representan objetos individuales y clases Compuesto que contienen elementos secundarios y delegan las operaciones en ellos:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interfaz de componente
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Hoja - representa archivos individuales
class File : public FileSystemItem {
std::string name;
int size;
public:
File(const std::string& n, int s) : name(n), size(s) {}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << name
<< " (" << size << " KB)\n";
}
int getSize() const override { return size; }
};
// Compuesto - contiene otros componentes
class Folder : public FileSystemItem {
std::string name;
std::vector<std::shared_ptr<FileSystemItem>> children;
public:
Folder(const std::string& n) : name(n) {}
void add(std::shared_ptr<FileSystemItem> item) {
children.push_back(item);
}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "[" << name << "]\n";
for (const auto& child : children) {
child->display(indent + 2);
}
}
int getSize() const override {
int total = 0;
for (const auto& child : children) {
total += child->getSize();
}
return total;
}
};El compuesto Folder almacena elementos secundarios e implementa operaciones iterando sobre ellos. Cuando llamas a getSize() en una carpeta, calcula recursivamente el tamaño total de todos los elementos que contiene. El código cliente no necesita saber si está trabajando con un archivo o una carpeta: ambos responden a la misma interfaz.
Usa Composite cuando necesites representar jerarquías de partes y conjuntos y quieras que los clientes traten de manera uniforme los objetos individuales y las composiciones.
Desafío
FácilVamos a crear un sistema de organigrama usando el patrón Composite. Crearás una jerarquía en la que tanto los empleados individuales como los departamentos (que contienen a otros empleados o subdepartamentos) se puedan tratar de manera uniforme. Esto refleja cómo se estructuran las empresas reales: los departamentos contienen personas y otros departamentos, formando una estructura de árbol.
Organizarás tu código en tres archivos:
OrgComponent.h: define la interfaz del componente que implementarán tanto los empleados como los departamentos.Crea una clase abstracta
OrgComponentcon:getName(): devuelve el nombre del componentegetSalary(): devuelve el salario total (para los empleados, su propio salario; para los departamentos, la suma de todos los salarios incluidos)display(int indent = 0): muestra el componente con la sangría adecuada
Incluye un destructor virtual.
Organization.h: implementa las clases hoja y compuesta.Crea una clase
Employee(la hoja) que almacene un nombre y un salario. Su métododisplay()debe imprimir la información del empleado con este formato:[indent spaces]- [name] ($[salary])Crea una clase
Department(el compuesto) que almacene un nombre y una colección de elementos secundariosOrgComponentusandostd::shared_ptr. Implementa:add(std::shared_ptr<OrgComponent> component): añade un elemento secundario al departamentogetSalary(): calcula recursivamente el salario total de todos los miembrosdisplay(): imprime el nombre del departamento entre corchetes y luego muestra todos los elementos secundarios con una sangría mayor (añade 2 espacios por nivel)
El formato de visualización del departamento debe ser:
[indent spaces][Department Name] [children displayed with indent + 2]main.cpp: crea y muestra una estructura organizativa.Lee cuatro entradas:
- Nombre de la empresa (cadena)
- Nombre del departamento (cadena)
- Nombre y salario del primer empleado (formato:
name,salary) - Nombre y salario del segundo empleado (formato:
name,salary)
Crea esta estructura: crea una empresa (departamento de nivel superior), añádele un subdepartamento y añade ambos empleados a ese subdepartamento. Luego, muestra toda la organización e imprime el salario total de la empresa.
Después de mostrar la estructura, imprime:
Total Salary: $[amount]
Por ejemplo, con las entradas TechCorp, Engineering, Alice,75000 y Bob,65000:
[TechCorp]
[Engineering]
- Alice ($75000)
- Bob ($65000)
Total Salary: $140000Con las entradas StartupInc, Development, Carol,80000 y Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Observa cómo getSalary() funciona de manera uniforme tanto si se llama en un empleado como en un departamento: el departamento agrega automáticamente los salarios de todos sus miembros. El código cliente no necesita distinguir entre empleados individuales y departamentos completos al calcular los totales o mostrar la jerarquía.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"
int main() {
// Lee las entradas
std::string companyName;
std::string departmentName;
std::string employee1Input;
std::string employee2Input;
std::getline(std::cin, companyName);
std::getline(std::cin, departmentName);
std::getline(std::cin, employee1Input);
std::getline(std::cin, employee2Input);
// Función lambda auxiliar para analizar el formato "name,salary"
auto parseEmployee = [](const std::string& input) -> std::pair<std::string, int> {
size_t commaPos = input.find(',');
std::string name = input.substr(0, commaPos);
int salary = std::stoi(input.substr(commaPos + 1));
return {name, salary};
};
auto [name1, salary1] = parseEmployee(employee1Input);
auto [name2, salary2] = parseEmployee(employee2Input);
// TODO: Crea la empresa como un Department de nivel superior
// TODO: Crea un subdepartamento
// TODO: Crea dos objetos Employee usando los datos analizados
// TODO: Añade los empleados al subdepartamento
// TODO: Añade el subdepartamento a la empresa
// TODO: Muestra toda la estructura de la organización
// TODO: Imprime el salario total con el formato: Total Salary: $[amount]
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de la POO
Archivos externosCompilación y construcción en C++Archivos de cabecera y archivos fuenteEspacios de nombres y ámbitoIntroducción a la POO en C++Clases y objetosEl puntero 'this'Métodos (funciones miembro)Atributos (miembros de datos)Fundamentos de constructores y destructoresRepaso: calculadora sencilla4Propiedades de clase
Miembros de instancia vs. estáticosGetters y settersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases friendRepaso: gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de constructores y destructoresSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualRepaso: jerarquía de empleados2Gestión de memoria
Memoria de pila vs. heapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Repaso: gestor de arreglos dinámicos5Encapsulación
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultación de informaciónStruct vs. classClases anidadas e internasRepaso: sistema de registros de estudiantes8Polimorfismo
Polimorfismo en compilación y en tiempo de ejecuciónSobrecarga de funcionesRepaso de las funciones virtualesFunciones virtuales purasClases abstractasDiseño de interfaces en C++Conversión dinámica y RTTIRepaso: calculadora de formas11Conceptos avanzados de POO
Composición frente a herenciaMixins mediante CRTPIdiom PimplBorrado de tiposClases enum y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y destructores
Constructor predeterminadoConstructor con parámetrosConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis detallado del destructorRegla de tres / cinco / ceroRepaso: clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujoSobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tipoRepaso: clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasFundamentos de SFINAE y rasgos de tiposRepaso: contenedor genéricoPractica por tu cuenta: Compilador de C++ online