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 tratar objetos individuais e grupos de objetos de maneira uniforme. Ele compõe objetos em estruturas de árvore nas quais tanto elementos únicos quanto contêineres 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 elementos filhos e delegam operações a eles:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interface do componente
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 composto Folder armazena os filhos e implementa operações iterando por eles. 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 Composite quando precisar representar hierarquias parte-todo e quiser que os clientes tratem objetos individuais e composições de maneira uniforme.
Desafio
FácilVamos criar um sistema de Organograma usando o padrão Composite. Você criará uma hierarquia na qual tanto funcionários individuais quanto departamentos (que contêm outros funcionários ou subdepartamentos) podem ser tratados uniformemente. 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
OrgComponentcom:getName(): retorna o nome do componentegetSalary(): retorna o salário total (para funcionários, o próprio salário; para departamentos, a soma de todos os salários contidos)display(int indent = 0): exibe o componente com o recuo adequado
Inclua um destrutor virtual.
Organization.h: implemente as classes folha e composta.Crie uma classe
Employee(a folha) que armazene um nome e um salário. O métododisplay()deve imprimir as informações do funcionário neste formato:[indent spaces]- [name] ($[salary])Crie uma classe
Department(a composta) que armazene um nome e uma coleção de filhosOrgComponentusandostd::shared_ptr. Implemente:add(std::shared_ptr<OrgComponent> component): adiciona um filho ao departamentogetSalary(): calcula recursivamente o salário total de todos os membrosdisplay(): imprime o nome do departamento entre colchetes e, em seguida, exibe todos os filhos com recuo aumentado (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: crie e exiba uma estrutura organizacional.Leia quatro entradas:
- Nome da empresa (string)
- Nome do departamento (string)
- Nome e salário do primeiro funcionário (formato:
name,salary) - Nome e salário do segundo funcionário (formato:
name,salary)
Crie esta estrutura: crie uma empresa (departamento de nível superior), adicione um subdepartamento a ela e adicione os dois funcionários a esse subdepartamento. Em seguida, exiba toda a organização e imprima o salário total da empresa.
Depois de 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: $140000Com as entradas StartupInc, Development, Carol,80000 e Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Observe como getSalary() funciona uniformemente quando chamado em um funcionário ou em um departamento: o departamento agrega automaticamente os salários de todos os seus membros. O código cliente não precisa distinguir entre funcionários individuais e departamentos inteiros ao calcular os totais ou exibir a hierarquia.
Experimente você mesmo
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"
int main() {
// Ler 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 os funcionários ao subdepartamento
// TODO: Adicionar o subdepartamento à empresa
// TODO: Exibir a estrutura completa da organização
// TODO: Imprimir o salário total no formato: Total Salary: $[amount]
return 0;
}
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
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online