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 objetos individuales y grupos de objetos de manera uniforme. 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 interfaz de usuario (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 hijos y les delegan operaciones:
#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 hijos e implementa operaciones iterando a través de 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 parte-todo y quieras que los clientes traten a los objetos individuales y a las composiciones de manera uniforme.
Desafío
FácilVamos a construir un sistema de Organigrama utilizando el patrón Composite. Crearás una jerarquía donde tanto los empleados individuales como los departamentos (que contienen otros empleados o subdepartamentos) puedan ser tratados 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 (leaf) y compuesta (composite).Crea una clase
Employee(la hoja) que almacene un nombre y un salario. Su métododisplay()debe imprimir la información del empleado en este formato:[indent spaces]- [name] ($[salary])Crea una clase
Department(el compuesto) que almacene un nombre y una colección de hijosOrgComponentutilizandostd::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, luego muestra a 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 (string)
- Nombre del departamento (string)
- 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 a 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 uniformemente ya sea que se llame sobre un empleado o un departamento: el departamento agrega automáticamente los salarios de todos sus miembros. El código del cliente no necesita distinguir entre empleados individuales y departamentos enteros al calcular totales o mostrar la jerarquía.
Hoja de referencia
El patrón Composite trata a los objetos individuales y a los grupos de objetos de manera uniforme, componiéndolos en estructuras de árbol donde tanto los elementos individuales como los contenedores comparten la misma interfaz.
El patrón tiene tres partes clave:
- Component: Una interfaz que define operaciones comunes
- Leaf: Clases que representan objetos individuales
- Composite: Clases que contienen hijos y les delegan operaciones
Ejemplo de implementación de un sistema de archivos:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interfaz Component
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Leaf - 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; }
};
// Composite - 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 composite almacena hijos utilizando std::shared_ptr e implementa operaciones iterando a través de ellos. Operaciones como getSize() funcionan de forma recursiva, calculando totales a lo largo de toda la jerarquía.
Utilice Composite cuando necesite representar jerarquías de parte-todo y desee que los clientes traten a los objetos individuales y a las composiciones de manera uniforme.
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: Crear la compañía como un Department de nivel superior
// TODO: Crear un subdepartamento
// TODO: Crear dos objetos Employee usando los datos analizados
// TODO: Añadir empleados al subdepartamento
// TODO: Añadir el subdepartamento a la compañía
// TODO: Mostrar toda la estructura de la organización
// TODO: Imprimir 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érico