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 donde tanto los elementos individuales como los contenedores de elementos comparten la misma interfaz. Esto es ideal para representar jerarquías como sistemas de archivos, organigramas o componentes de UI.
El patrón tiene tres partes clave: una interfaz Component que define operaciones comunes, clases Leaf que representan objetos individuales y clases Composite que contienen elementos secundarios y delegan las operaciones en ellos:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interfaz del 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 contenidos. 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 los objetos individuales y las composiciones de manera uniforme.
Desafío
FácilConstruyamos 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 otros empleados o subdepartamentos) puedan tratarse de manera uniforme. Esto refleja cómo están estructuradas 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 contenidos)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(la compuesta) que almacene un nombre y una colección de hijosOrgComponentusandostd::shared_ptr. Implementa:add(std::shared_ptr<OrgComponent> component): añade un hijo al departamentogetSalary(): calcula recursivamente el salario total de todos los miembrosdisplay(): imprime el nombre del departamento entre corchetes y, después, muestra todos los hijos 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: construye 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)
Construye esta estructura: crea una empresa (departamento de nivel superior), añádele un subdepartamento y añade ambos empleados a ese subdepartamento. Después, 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 cuando se llama en un empleado como cuando se llama 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() {
// Leer 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);
// 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 en 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 OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes 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 por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online