Menu
Coddy logo textTech

Padrão Composite

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 101 de 104.

O padrão Composite permite que você trate objetos individuais e grupos de objetos de forma uniforme. Ele compõe objetos em estruturas de árvore onde tanto elementos únicos quanto containers de elementos compartilham a mesma interface. Isso é ideal para representar hierarquias como sistemas de arquivos, organogramas ou componentes de UI.

O padrão tem três partes principais: uma interface Component que define operações comuns, classes Leaf que representam objetos individuais e classes Composite que contêm filhos e delegam operações a eles:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// Interface Component
class FileSystemItem {
public:
    virtual void display(int indent = 0) const = 0;
    virtual int getSize() const = 0;
    virtual ~FileSystemItem() = default;
};

// Leaf - representa arquivos individuais
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 - contém outros 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;
    }
};

O composite Folder armazena filhos e implementa operações iterando através deles. Quando você chama getSize() em uma pasta, ele calcula recursivamente o tamanho total de todos os itens contidos. O código cliente não precisa saber se está trabalhando com um arquivo ou uma pasta — ambos respondem à mesma interface.

Use o Composite quando você precisar representar hierarquias parte-todo e quiser que os clientes tratem objetos individuais e composições de forma uniforme.

challenge icon

Desafio

Fácil

Vamos construir um sistema de Organograma usando o padrão Composite. Você criará uma hierarquia onde tanto funcionários individuais quanto departamentos (que contêm outros funcionários ou subdepartamentos) podem ser tratados de forma uniforme. Isso reflete como as empresas reais são estruturadas — departamentos contêm pessoas e outros departamentos, formando uma estrutura de árvore.

Você organizará seu código em três arquivos:

  • OrgComponent.h: Defina a interface do componente que tanto os funcionários quanto os departamentos implementarão.

    Crie uma classe abstrata OrgComponent com:

    • getName() — retorna o nome do componente
    • getSalary() — retorna o salário total (para funcionários, seu próprio salário; para departamentos, a soma de todos os salários contidos)
    • display(int indent = 0) — exibe o componente com a indentação adequada

    Inclua um destrutor virtual.

  • Organization.h: Implemente as classes leaf (folha) e composite (composta).

    Crie uma classe Employee (a folha) que armazena um nome e um salário. Seu método display() deve imprimir as informações do funcionário neste formato:

    [indent spaces]- [name] ($[salary])

    Crie uma classe Department (a composta) que armazena um nome e uma coleção de filhos OrgComponent usando std::shared_ptr. Implemente:

    • add(std::shared_ptr<OrgComponent> component) — adiciona um filho ao departamento
    • getSalary() — calcula recursivamente o salário total de todos os membros
    • display() — imprime o nome do departamento entre colchetes e, em seguida, exibe todos os filhos com indentação aumentada (adicione 2 espaços por nível)

    O formato de exibição do departamento deve ser:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: Construa e exiba uma estrutura organizacional.

    Leia quatro entradas:

    1. Nome da empresa (string)
    2. Nome do departamento (string)
    3. Nome e salário do primeiro funcionário (formato: name,salary)
    4. Nome e salário do segundo funcionário (formato: name,salary)

    Construa esta estrutura: Crie uma empresa (departamento de nível superior), adicione um subdepartamento a ela e adicione ambos os funcionários a esse subdepartamento. Em seguida, exiba toda a organização e imprima o salário total da empresa.

    Após exibir a estrutura, imprima:

    Total Salary: $[amount]

Por exemplo, com as entradas TechCorp, Engineering, Alice,75000 e Bob,65000:

[TechCorp]
  [Engineering]
    - Alice ($75000)
    - Bob ($65000)
Total Salary: $140000

Com as entradas StartupInc, Development, Carol,80000 e Dave,70000:

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

Observe como o getSalary() funciona uniformemente, seja chamado em um funcionário ou em um departamento — o departamento agrega automaticamente os salários de todos os seus membros. O código do cliente não precisa distinguir entre funcionários individuais e departamentos inteiros ao calcular totais ou exibir a hierarquia.

Folha de consulta

O padrão Composite trata objetos individuais e grupos de objetos de forma uniforme, compondo-os em estruturas de árvore onde tanto elementos únicos quanto containers compartilham a mesma interface.

O padrão possui três partes principais:

  • Component: Uma interface que define operações comuns
  • Leaf: Classes que representam objetos individuais
  • Composite: Classes que contêm filhos e delegam operações a eles

Exemplo de implementação de um sistema de arquivos:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// Interface Component
class FileSystemItem {
public:
    virtual void display(int indent = 0) const = 0;
    virtual int getSize() const = 0;
    virtual ~FileSystemItem() = default;
};

// Leaf - representa arquivos individuais
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 - contém outros 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;
    }
};

O composite armazena filhos usando std::shared_ptr e implementa operações iterando através deles. Operações como getSize() funcionam recursivamente, calculando totais em toda a hierarquia.

Use o Composite quando precisar representar hierarquias parte-todo e quiser que os clientes tratem objetos individuais e composições de forma uniforme.

Experimente você mesmo

#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"

int main() {
    // Ler as 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 analisar o 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: Criar a empresa como um Department de nível superior
    
    // TODO: Criar um subdepartamento
    
    // TODO: Criar dois objetos Employee usando os dados analisados
    
    // TODO: Adicionar funcionários ao subdepartamento
    
    // TODO: Adicionar o subdepartamento à empresa
    
    // TODO: Exibir toda a estrutura da organização
    
    // TODO: Imprimir o salário total no formato: Total Salary: $[amount]
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos