Menu
CoddyTech

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.

challenge icon

Desafío

Fácil

Vamos 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 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 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étodo display() 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 secundarios OrgComponent usando std::shared_ptr. Implementa:

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

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

Practica por tu cuenta: Compilador de C++ online