Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 OrgComponent con:

    • getName() — devuelve el nombre del componente
    • getSalary() — 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étodo display() 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 hijos OrgComponent utilizando std::shared_ptr. Implementa:

    • add(std::shared_ptr<OrgComponent> component) — añade un hijo al departamento
    • getSalary() — calcula recursivamente el salario total de todos los miembros
    • display() — 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:

    1. Nombre de la empresa (string)
    2. Nombre del departamento (string)
    3. Nombre y salario del primer empleado (formato: name,salary)
    4. 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: $140000

Con las entradas StartupInc, Development, Carol,80000 y Dave,70000:

[StartupInc]
  [Development]
    - Carol ($80000)
    - Dave ($70000)
Total Salary: $150000

Observa 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;
}
quiz iconPonte a prueba

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