Padrão Composite
Parte da seção Programação Orientada a Objetos do Journey de PHP da Coddy — lição 80 de 91.
O Composite Pattern é um padrão de projeto estrutural que permite compor objetos em estruturas de árvore e, em seguida, trabalhar com essas estruturas como se fossem objetos individuais. Ele é perfeito para representar hierarquias parte-todo, onde você deseja tratar objetos únicos e grupos de objetos de forma uniforme.
Considere um sistema de arquivos. Uma pasta pode conter arquivos e outras pastas.
Quando você calcula o tamanho total, você deseja chamar o mesmo método tanto em arquivos quanto em pastas, e as pastas devem somar recursivamente seus conteúdos. O Padrão Composite torna isso transparente ao fornecer a mesma interface tanto para as folhas (arquivos) quanto para os compostos (pastas).
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
Agora você pode construir estruturas de árvore e tratá-las de forma uniforme:
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize();
Saída:
1810O código cliente chama getSize() sem se preocupar se está lidando com um único arquivo ou uma estrutura de pastas profundamente aninhada. O padrão lida com a recursão internamente, tornando as hierarquias complexas fáceis de trabalhar.
Desafio
FácilVamos construir um sistema de organograma usando o Composite Pattern. As empresas possuem uma estrutura hierárquica onde os departamentos podem conter funcionários e outros subdepartamentos — uma estrutura de árvore perfeita onde você deseja calcular totais de forma uniforme em toda a organização.
Você organizará seu código em quatro arquivos:
OrganizationUnit.php— Defina uma interfaceOrganizationUnitque tanto funcionários individuais quanto departamentos implementarão. Ela deve ter dois métodos:getName(): stringegetSalary(): int. Esta interface comum permite que você trate funcionários individuais e departamentos inteiros da mesma maneira ao calcular custos.Employee.php— Inclua a interface OrganizationUnit e crie uma classeEmployeeque a implemente. Um funcionário é um nó folha em sua árvore organizacional — ele tem um nome e um salário, mas não pode conter outras unidades. Use a promoção de construtor para aceitar o nome e o salário, e implemente ambos os métodos da interface.Department.php— Inclua a interface OrganizationUnit e crie uma classeDepartmentque a implemente. Um departamento é um composto que pode conter tanto funcionários quanto outros departamentos. Seu departamento deve:- Aceitar um nome em seu construtor
- Ter um método
add(OrganizationUnit $unit): voidpara adicionar funcionários ou subdepartamentos - Implementar
getSalary()para retornar o salário total de todas as unidades que ele contém (somando recursivamente tudo o que estiver dentro)
main.php— Inclua os arquivos Employee e Department. Você receberá uma entrada: uma string JSON representando a estrutura da organização.O JSON estará neste formato:
{"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}Cada chave é o nome de um departamento, e seu valor é um array de funcionários com seus nomes e salários.
Crie um
Departmentraiz chamado"Company". Para cada departamento no JSON, crie umDepartment, adicione seus funcionários e, em seguida, adicione esse departamento à Company. Por fim, imprima o salário total da empresa neste formato:Total Salary: [amount]
A beleza do Composite Pattern é que chamar getSalary() no departamento Company calcula automaticamente a soma de todos os departamentos e funcionários aninhados — o código do cliente não precisa conhecer a estrutura interna.
Folha de consulta
O Composite Pattern é um padrão de design estrutural que compõe objetos em estruturas de árvore e permite tratar objetos individuais e grupos de forma uniforme.
É ideal para hierarquias do tipo parte-todo, onde você deseja a mesma interface tanto para nós folha quanto para nós compostos.
Componentes Principais
- Interface Component — Define operações comuns tanto para folhas quanto para compostos
- Leaf (Folha) — Representa objetos individuais que não podem conter outros objetos
- Composite (Composto) — Contém filhos (folhas ou outros compostos) e implementa operações delegando-as aos filhos
Exemplo: Sistema de Arquivos
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
Uso
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize(); // 1810
O cliente chama getSize() uniformemente tanto em arquivos quanto em pastas. O composto lida com a recursão internamente, tornando hierarquias complexas simples de trabalhar.
Experimente você mesmo
<?php
require_once 'Employee.php';
require_once 'Department.php';
// Lê a entrada JSON
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);
// TODO: Criar um Departamento raiz chamado "Company"
// TODO: Percorrer cada departamento nos dados JSON
// Para cada departamento:
// - Criar um novo objeto Department
// - Adicionar cada funcionário a esse departamento
// - Adicionar o departamento à Company
// TODO: Imprimir o salário total no formato: Total Salary: [amount]
?>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 ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave $thisMétodosPropriedadesConstrutor (__construct)Destrutor (__destruct)Recapitulação - Calculadora Simples4Herança
Herança BásicaA Palavra-chave parent::Sobrescrita de MétodoA Palavra-chave finalClasses AbstratasRecapitulação - Hierarquia de Funcionários7Encapsulamento
Público, Protegido, PrivadoModificadores de Acesso em DetalhesGetters e SettersOcultação de InformaçãoPromoção de Construtor (8.0)Recapitulação - Sistema de Registro de Alunos10Conceitos Avançados de POO
Composição vs HerançaInjeção de DependênciaClasses AnônimasEnums (PHP 8.1)Fibers (PHP 8.1)Mergulho Profundo em Clonagem de ObjetosGeradores e Iteradores13Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Repository2Namespaces e Autoloading
Introdução a NamespacesA Palavra-chave usePadrão de Autoloading PSR-4Autoloader do ComposerRecapitulação - Projeto Organizado5Interfaces e Contratos
Introdução a InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesInterface vs Classe AbstrataType Hinting com InterfacesRecapitulação - Calculadora de Formas8Métodos Mágicos
Introdução aos Métodos Mágicos__toString e __debugInfo__get, __set, __isset, __unset__call e __callStatic__clone e Clonagem de Objetos__serialize e __unserializeRecapitulação - Coleção Personalizada11Sistema de Tipos e Tratamento de Erros
Declarações de TiposTipos AnuláveisTipos de União e InterseçãoClasses de ExceçãoHierarquia de Exceções PersonalizadasTry, Catch, FinallyRecapitulação - Validador de Formulário14Projeto: Gerenciamento de Biblioteca
Visão Geral do ProjetoClasses Livro e Usuário3Propriedades de Classe
Propriedades de Instância vs EstáticasConstantes em ClassesMétodos e Propriedades EstáticosPropriedades Privadas e ProtegidasPropriedades Readonly (PHP 8.1)Recapitulação - Gerenciador de Conta Bancária6Polimorfismo
Sobrescrita de Métodos RevisitadaPolimorfismo via InterfacesType Hinting e Union TypesLate Static BindingRecapitulação - Processador de Pagamentos9Traits
Introdução a TraitsUsando Múltiplos TraitsResolução de Conflitos de TraitsMétodos Abstratos em TraitsTraits vs Herança12Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão ObserverPadrão Strategy