Pular para o conteúdo principal

Composite

Visão Geral

Composite organiza objetos em estruturas de árvore e permite que o cliente trate objetos individuais e composições de forma uniforme.

O ganho é específico: o cliente deixa de precisar saber se está lidando com uma folha ou com um nó. Onde havia condicionais, passa a haver uma chamada.

Problema

Uma estrutura hierárquica em que um elemento pode conter outros do mesmo tipo: diretórios com arquivos e subdiretórios, grupos de elementos gráficos, itens de menu com submenus, expressões com subexpressões.

Sem o padrão, todo código que percorre a estrutura precisa distinguir:

se for arquivo: somar tamanho
se for diretório: para cada filho, repetir

Esse condicional se replica em cada operação — calcular tamanho, renderizar, contar, buscar. Adicionar um tipo de nó exige tocar todos eles.

Conceitos Centrais

A estrutura

O composto implementa a mesma interface que a folha e delega aos filhos. A recursão fica dentro da estrutura, não no cliente.

A decisão central: uniformidade ou segurança

O GoF apresenta duas variantes, e a escolha entre elas é o trade-off do padrão.

Transparente — a interface Componente declara adicionar e remover. Cliente trata tudo igual; a folha precisa lidar com operações que não fazem sentido para ela, tipicamente lançando exceção.

Segura — apenas o composto tem adicionar e remover. Não há operação sem sentido; o cliente precisa verificar o tipo para compor.

Transparente ganha em uniformidade e perde em segurança de tipo. Segura, o inverso. Não há terceira opção, e escolher exige saber se o cliente compõe ou apenas percorre.

Quando o cliente só percorre, a variante segura não custa nada e é preferível.

Composite e recursão

A estrutura é naturalmente recursiva, e isso traz duas preocupações reais: profundidade — pilha em árvores muito fundas — e ciclos, que produzem recursão infinita se a estrutura permitir que um nó contenha um ancestral.

Quando Usar

  • Existe hierarquia parte-todo genuína no domínio.
  • O cliente deve tratar folhas e composições igualmente.
  • Operações se aplicam recursivamente a toda a estrutura.
  • Novos tipos de folha aparecem com frequência.

Quando Não Usar

Quando a hierarquia não é parte-todo. Herança não é composição. Se um tipo não contém outros do mesmo tipo, o padrão não se aplica.

Quando folha e composto têm comportamento muito diferente. Forçar uma interface comum produz métodos sem sentido de um dos lados, e o cliente acaba verificando o tipo mesmo assim — perdendo o benefício e mantendo o custo.

Quando a estrutura é rasa e fixa. Dois níveis conhecidos não justificam a generalidade.

Quando a segurança de tipo importa mais que a uniformidade. Ver a decisão acima.

Quando operações precisam de contexto do caminho. Se o comportamento de um nó depende de quem são seus ancestrais, a uniformidade quebra e o padrão passa a atrapalhar.

Alternativas

  • Lista simples — quando a estrutura é rasa.
  • Visitor — quando as operações variam mais que os tipos de nó; frequentemente usado junto com Composite.
  • Iterator — quando o percurso é a única necessidade.
  • Estrutura de dados sem hierarquia de classes — uma árvore genérica com dados nos nós.

Trade-offs

CompositeCondicional no cliente
Cliente uniformeCondicional replicado
Tipo de folha novo não toca o clienteToca todos os condicionais
Interface com operações sem sentido para folhasTipos precisos
Recursão encapsuladaExplícita e visível
Percurso implícito, difícil de controlarControle total

Modos de Falha

Folha que lança exceção. Consequência da variante transparente, e uma violação de Liskov declarada.

Ciclo na estrutura. Recursão infinita.

Profundidade excessiva. Estouro de pilha em percurso recursivo.

Composto vazio tratado como folha. Ambiguidade que produz defeitos sutis.

Operação cara escondida. Uma chamada simples percorre milhares de nós, e nada no código sugere isso.

Erros Comuns

Aplicar a hierarquia de herança. Não é o mesmo que hierarquia parte-todo.

Escolher a variante sem pensar. É a decisão do padrão.

Não tratar ciclos. Se a estrutura permite, alguém vai criar um.

Ignorar o custo de percurso. Uma operação uniforme pode ser O(n) sem aviso.

Onde ele aparece na prática

Árvores de interface gráfica. Um contêiner é um componente que contém componentes. Renderizar, medir e propagar eventos são operações uniformes sobre a árvore.

Sistemas de arquivos. Diretórios e arquivos com operações comuns — tamanho, permissões, caminho.

Árvores sintáticas. Uma expressão contém subexpressões; avaliar é recursivo. É onde Composite e Visitor aparecem juntos com mais frequência.

Estruturas de documento. DOM em navegadores: nós que contêm nós, com operações uniformes.

Nos quatro, a hierarquia parte-todo é intrínseca ao domínio — ninguém a inventou para aplicar o padrão. É o sinal de que Composite é adequado: a árvore já existe no problema.

Exemplo Real

Um sistema de permissões modelava grupos que contêm usuários e outros grupos. Verificar se alguém tem uma permissão exigia percorrer recursivamente.

A primeira implementação usou Composite transparente: Principal com adicionar e remover, e Usuario lançando exceção nos dois.

O problema apareceu quando a interface administrativa passou a construir a estrutura: ela precisava verificar o tipo antes de compor, o que anulava a uniformidade — e ainda mantinha as exceções.

A mudança para a variante segura removeu adicionar e remover de Principal. O código de verificação de permissão, que só percorre, continuou uniforme. O código administrativo, que compõe, passou a trabalhar com Grupo explicitamente.

Descobriu-se que os dois clientes tinham necessidades diferentes, e que a variante transparente estava tentando servir aos dois com uma interface só.

Um ciclo também apareceu: um administrador colocou um grupo dentro de um descendente dele. A verificação passou a rastrear os nós visitados, o que deveria ter sido feito desde o início.

Conceitos Relacionados

  • Decorator — estrutura parecida, propósito diferente.
  • Visitor — operações sobre a estrutura.
  • Iterator — percurso.

Exercício Prático

Procure no seu sistema estruturas em que um elemento contém elementos do mesmo tipo.

Para cada uma, verifique: existe condicional de tipo replicado nos percursos? A estrutura permite ciclo? Quanto custa a operação mais comum em número de nós?

Perguntas de Entrevista

  • Quais são as duas variantes de Composite e o que se troca entre elas?
  • Como Composite difere de uma hierarquia de herança comum?
  • Que riscos a recursão traz neste padrão?

Para Aprofundar

  • Gamma, Erich et al. Design Patterns. Addison-Wesley, 1994.