Composite
Introdução
Seção intitulada “Introdução”O Composite é um padrão de projeto estrutural que permite organizar objetos em uma estrutura de árvore e tratar tanto elementos simples quanto grupos de elementos por meio da mesma interface. Em vez de o cliente precisar diferenciar manualmente um objeto isolado de um conjunto inteiro, ele conversa com todos os elementos da mesma forma.
Esse padrão aparece quando o domínio possui composições naturais, como pastas e arquivos, categorias e subcategorias, menus e submenus, ou caixas que contêm produtos e outras caixas. O Composite existe para simplificar o código cliente e para permitir operações recursivas sobre estruturas hierárquicas.
Problema
Seção intitulada “Problema”Imagine um sistema de e-commerce que precisa calcular o preço total de um pedido. Esse pedido pode conter:
- produtos avulsos
- kits promocionais
- caixas com vários itens
- caixas que contêm outras caixas
Sem um modelo adequado, o cliente tende a criar verificações como:
- se for produto, some o preço direto
- se for caixa, percorra os itens internos
- se houver outra caixa, repita a lógica novamente
- se houver novo tipo de agrupamento, altere o cliente outra vez
Esse desenho gera problemas práticos:
- o código cliente fica cheio de condicionais
- novas estruturas hierárquicas exigem mudanças em vários pontos
- a recursão fica espalhada pelo sistema
- o acoplamento com classes concretas aumenta
- a manutenção se torna mais difícil com o crescimento da árvore
Em resumo, o problema não é apenas calcular algo. O problema é tratar uniformemente objetos simples e compostos em uma estrutura hierárquica.
Solução
Seção intitulada “Solução”O Composite resolve isso definindo uma abstração comum para todos os elementos da árvore.
- Crie uma interface ou classe base chamada, por exemplo,
ComponentePedido. - Faça objetos simples, como
Produto, implementarem essa abstração. - Faça objetos compostos, como
CaixaouKit, implementarem a mesma abstração. - No objeto composto, mantenha uma coleção de filhos do tipo da abstração comum.
- Delegue a operação para os filhos e agregue o resultado.
Assim, quando o cliente pedir calcularPreco(), tanto uma folha quanto um grupo saberão responder. Uma folha devolve seu próprio valor. Um composite percorre seus filhos, soma os resultados e retorna o total.
O ponto central do padrão é este: o cliente não precisa saber se está lidando com um item único ou com um grupo inteiro.
Analogia
Seção intitulada “Analogia”Pense na estrutura de pastas do sistema operacional. Um arquivo é um elemento simples. Uma pasta é um elemento composto, porque pode conter arquivos e outras pastas.
Quando você pede o tamanho total de uma pasta, o sistema percorre tudo o que está dentro dela, incluindo subpastas. Para quem usa, a interação continua natural: tanto um arquivo quanto uma pasta são elementos do sistema de arquivos, mas uma pasta agrega outros elementos internamente.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use Composite quando:
- o domínio puder ser representado como árvore
- houver elementos simples e agrupamentos aninhados
- o cliente precisar tratar itens e grupos de forma uniforme
- operações recursivas forem comuns, como somar, renderizar ou percorrer
- a estrutura puder crescer com vários níveis de composição
Exemplos reais em software moderno:
- menus com submenus em interfaces web
- pastas e arquivos em gerenciadores de documentos
- componentes visuais com containers e widgets filhos
- categorias e subcategorias em marketplaces
- comentários com respostas encadeadas em plataformas online
Anti-padrão / Mau uso
Seção intitulada “Anti-padrão / Mau uso”Composite pode ser mal utilizado quando a equipe tenta forçar uma hierarquia onde ela não existe de verdade. Alguns erros frequentes são:
- criar composites sem haver necessidade de composição recursiva
- colocar métodos demais na interface comum, tornando-a confusa para folhas
- usar o padrão em estruturas pequenas e estáveis demais
- misturar responsabilidades de navegação, negócio e persistência no mesmo composite
As consequências incluem excesso de classes, dificuldade para entender a interface base e objetos folha precisando implementar operações que fazem pouco sentido.
Composite é valioso quando a árvore faz parte do problema. Se a estrutura não for hierárquica, o padrão pode adicionar mais complexidade do que benefício.
Como implementar
Seção intitulada “Como implementar”Uma forma incremental de implementar Composite é:
- Identifique qual elemento comum existe em toda a estrutura.
- Defina uma interface com a operação principal, como
calcularPreco()ourenderizar(). - Crie uma ou mais classes folha para os elementos simples.
- Crie uma classe composite para os elementos que contêm filhos.
- Armazene os filhos usando a abstração comum.
- No composite, percorra os filhos e combine os resultados.
- Exponha métodos de
adicionareremoverquando fizer sentido.
Antes de escrever o código, vale responder: o cliente realmente precisa ignorar a diferença entre folha e composite? Se sim, o padrão costuma ser uma boa escolha.
Exemplo em código
Seção intitulada “Exemplo em código”No exemplo abaixo, um sistema de loja calcula o total de itens avulsos e caixas compostas de forma uniforme.
interface ItemPedido { double calcularPreco();}
class Produto implements ItemPedido { private String nome; private double preco;
public Produto(String nome, double preco) { this.nome = nome; this.preco = preco; }
@Override public double calcularPreco() { return preco; }}
class Caixa implements ItemPedido { private List<ItemPedido> itens = new ArrayList<>();
public void adicionar(ItemPedido item) { itens.add(item); }
@Override public double calcularPreco() { double total = 0; for (ItemPedido item : itens) { total += item.calcularPreco(); } return total; }}Nesse código, Produto é a folha e Caixa é o composite. Ambos implementam ItemPedido, então o cliente pode chamar calcularPreco() sem conhecer o tipo concreto do objeto.
Essa uniformidade reduz condicionais e facilita a extensão da estrutura com novos tipos de folha ou novos composites.
- simplifica o tratamento de estruturas hierárquicas
- reduz condicionais no código cliente
- favorece polimorfismo e recursão
- facilita a extensão com novos tipos de componentes
- melhora a legibilidade quando o domínio é naturalmente em árvore
Contras
Seção intitulada “Contras”- pode tornar a interface base genérica demais
- aumenta a quantidade de classes no sistema
- pode dificultar validações específicas de folhas e composites
- nem toda estrutura com coleções justifica o uso do padrão
Relações com outros padrões/conceitos
Seção intitulada “Relações com outros padrões/conceitos”O Composite se relaciona com outros padrões importantes:
- Decorator: ambos usam composição recursiva, mas Decorator encapsula um único componente e adiciona comportamento; Composite organiza vários filhos em árvore
- Visitor: pode ser usado para executar operações em toda a árvore sem colocar toda a lógica dentro dos componentes
- Iterator: ajuda a percorrer estruturas Composite de forma controlada
- Builder: pode ser usado para montar árvores Composite complexas passo a passo
- Flyweight: pode ajudar quando muitas folhas compartilham dados e o consumo de memória se torna relevante
Essas relações mostram que Composite não serve apenas para agrupamento. Ele também abre espaço para outras estratégias de navegação, construção e extensão da árvore.
Implementações alternativas (quando aplicável)
Seção intitulada “Implementações alternativas (quando aplicável)”As formas mais comuns de aplicar Composite são:
- interface comum mínima com operações seguras para folhas e composites
- métodos de
adderemoveapenas no composite - métodos de gerenciamento de filhos na interface base, quando a uniformidade do cliente for mais importante
- composites especializados para regras distintas, como
Menu,Secao,PacoteouCategoria
Em Java, uma abordagem comum é usar uma interface para a operação principal e deixar apenas o composite expor métodos de montagem da árvore.
Exemplo completo
Seção intitulada “Exemplo completo”Agora veja um caso mais realista em um sistema acadêmico. A plataforma virtual permite montar módulos de curso com aulas avulsas e unidades que agrupam outras partes do conteúdo.
import java.util.ArrayList;import java.util.List;
interface ComponenteCurso { int calcularCargaHoraria(); void exibir(String prefixo);}
class Aula implements ComponenteCurso { private String titulo; private int horas;
public Aula(String titulo, int horas) { this.titulo = titulo; this.horas = horas; }
@Override public int calcularCargaHoraria() { return horas; }
@Override public void exibir(String prefixo) { System.out.println(prefixo + "Aula: " + titulo + " (" + horas + "h)"); }}
class Unidade implements ComponenteCurso { private String nome; private List<ComponenteCurso> componentes = new ArrayList<>();
public Unidade(String nome) { this.nome = nome; }
public void adicionar(ComponenteCurso componente) { componentes.add(componente); }
@Override public int calcularCargaHoraria() { int total = 0; for (ComponenteCurso componente : componentes) { total += componente.calcularCargaHoraria(); } return total; }
@Override public void exibir(String prefixo) { System.out.println(prefixo + "Unidade: " + nome); for (ComponenteCurso componente : componentes) { componente.exibir(prefixo + " "); } }}
public class PlataformaCursos { public static void main(String[] args) { Aula aula1 = new Aula("Introdução ao Composite", 2); Aula aula2 = new Aula("Exemplo prático", 1); Aula aula3 = new Aula("Exercícios", 1);
Unidade modulo1 = new Unidade("Fundamentos"); modulo1.adicionar(aula1); modulo1.adicionar(aula2);
Unidade curso = new Unidade("Padrões Estruturais"); curso.adicionar(modulo1); curso.adicionar(aula3);
curso.exibir(""); System.out.println("Carga horária total: " + curso.calcularCargaHoraria() + "h"); }}Nesse exemplo, Aula é uma folha e Unidade é um composite. O cliente monta uma árvore de conteúdos e consegue calcular a carga horária total ou exibir a estrutura completa sem diferenciar cada nível manualmente.
Esse tipo de modelagem funciona bem quando o domínio possui partes e subpartes que podem ser tratadas como um todo coerente.