Padrões estruturais: Composite e Bridge

1. Introdução aos Padrões Estruturais Composite e Bridge

Padrões estruturais na Arquitetura de Software tratam da composição de classes e objetos para formar estruturas maiores e mais complexas. Eles respondem à pergunta fundamental: "Como organizamos nossas entidades de software para que o sistema seja flexível, extensível e de fácil manutenção?"

Composite e Bridge são dois padrões estruturais que resolvem problemas distintos, mas igualmente críticos. O Composite lida com a representação de hierarquias parte-todo, permitindo que objetos individuais e composições de objetos sejam tratados uniformemente. O Bridge, por sua vez, aborda o desacoplamento entre uma abstração e sua implementação, permitindo que ambas variem independentemente.

Quando a complexidade das hierarquias de classes começa a gerar explosão combinatória ou quando estruturas recursivas precisam ser tratadas de forma transparente, esses padrões tornam-se ferramentas essenciais no arsenal do arquiteto de software.

2. Padrão Composite: Composição Hierárquica de Objetos

O Composite estrutura objetos em árvores para representar hierarquias parte-todo. Sua estrutura é composta por três elementos:

  • Component: interface comum que declara operações para objetos da composição
  • Leaf: objeto folha que implementa o comportamento primitivo
  • Composite: objeto que armazena componentes filhos e implementa operações delegando-as aos filhos

O mecanismo de recursão é o coração do Composite. Operações como render() ou calcularTamanho() são chamadas no Composite, que as delega recursivamente para todos os filhos.

// Interface Component
interface ComponenteGrafico {
    void renderizar()
    void adicionar(ComponenteGrafico c)
    void remover(ComponenteGrafico c)
}

// Leaf
class Botao implements ComponenteGrafico {
    void renderizar() { /* renderiza botão */ }
    void adicionar(ComponenteGrafico c) { /* não aplicável */ }
    void remover(ComponenteGrafico c) { /* não aplicável */ }
}

// Composite
class Painel implements ComponenteGrafico {
    List<ComponenteGrafico> filhos
    void renderizar() {
        for (filho in filhos) filho.renderizar()
    }
    void adicionar(ComponenteGrafico c) { filhos.add(c) }
    void remover(ComponenteGrafico c) { filhos.remove(c) }
}

// Uso
ComponenteGrafico janela = new Painel()
ComponenteGrafico painel = new Painel()
painel.adicionar(new Botao())
janela.adicionar(painel)
janela.renderizar() // Renderiza recursivamente

Em um sistema de interface gráfica, uma Janela (Composite) contém Painéis (Composite) que contêm Botões (Leaf). A operação renderizar() percorre toda a árvore, garantindo que cada elemento seja desenhado corretamente.

3. Aplicações Avançadas do Composite em Arquitetura

O padrão Composite encontra aplicações naturais em diversos domínios arquiteturais:

Gerenciamento de estruturas de documentos: Editores de texto e processadores XML representam documentos como árvores de elementos. Cada nó pode ser um parágrafo, seção ou tabela (Composite), enquanto caracteres e imagens são folhas. Operações como exportarParaHTML() percorrem recursivamente a árvore.

Sistemas de arquivos e diretórios: Arquivos (Leaf) e Diretórios (Composite) compartilham operações como calcularTamanho() e buscarArquivos(). Um diretório delega essas operações para seus filhos, somando tamanhos ou agregando resultados de busca.

Trade-offs importantes:
- Complexidade de navegação em árvores profundas pode impactar desempenho
- Transparência vs. segurança: operações como adicionar() em Leafs geram exceções em tempo de execução

4. Padrão Bridge: Separando Abstração da Implementação

O Bridge desacopla uma abstração de sua implementação, permitindo que ambas evoluam independentemente. Sua estrutura é composta por:

  • Abstraction: define a interface de alto nível
  • RefinedAbstraction: estende a abstração base
  • Implementor: define a interface para implementações concretas
  • ConcreteImplementor: implementa o Implementor

O princípio de desacoplamento evita a explosão combinatória de classes. Sem Bridge, teríamos N × M classes para N abstrações e M implementações. Com Bridge, temos N + M classes.

// Implementor
interface APIDesenhista {
    void desenharCirculo(float x, float y, float raio)
    void desenharQuadrado(float x, float y, float lado)
}

// ConcreteImplementors
class OpenGLDesenhista implements APIDesenhista {
    void desenharCirculo(float x, float y, float raio) { /* OpenGL */ }
    void desenharQuadrado(float x, float y, float lado) { /* OpenGL */ }
}

class DirectXDesenhista implements APIDesenhista {
    void desenharCirculo(float x, float y, float raio) { /* DirectX */ }
    void desenharQuadrado(float x, float y, float lado) { /* DirectX */ }
}

// Abstraction
abstract class Forma {
    protected APIDesenhista api
    Forma(APIDesenhista api) { this.api = api }
    abstract void desenhar()
}

// RefinedAbstraction
class Circulo extends Forma {
    float x, y, raio
    void desenhar() { api.desenharCirculo(x, y, raio) }
}

// Uso
Forma circulo = new Circulo(5, 5, 10, new OpenGLDesenhista())
circulo.desenhar() // Desenha com OpenGL

5. Bridge na Prática: Cenários de Arquitetura de Software

Adaptação multiplataforma: Abstrações de interface do usuário (janelas, menus, diálogos) com implementações específicas para Windows, Linux e macOS. O código da abstração permanece idêntico, enquanto as implementações lidam com peculiaridades de cada plataforma.

Persistência e drivers de banco de dados: A abstração Repositorio define operações como salvar(), buscar(), remover(). Implementações concretas usam SQL, NoSQL ou arquivos. A variação independente permite que novas estratégias de persistência sejam adicionadas sem modificar a lógica de negócio.

Variação independente: Bridge permite evoluir abstrações e implementações sem impacto mútuo. Podemos adicionar novas formas geométricas sem modificar APIs de desenho, ou novas APIs sem modificar as formas existentes.

6. Comparação entre Composite e Bridge

Semelhanças: Ambos promovem baixo acoplamento e flexibilidade estrutural, utilizando composição sobre herança.

Diferenças fundamentais:
- Composite foca em hierarquias parte-todo e tratamento uniforme de objetos individuais e compostos
- Bridge foca em separar duas dimensões de variação: abstração e implementação

Critérios de escolha:
- Use Composite quando o domínio possui estruturas recursivas naturais (árvores, documentos, sistemas de arquivos)
- Use Bridge quando duas dimensões de variação precisam ser independentes (formas × APIs, interfaces × plataformas)

7. Integração com Padrões Vizinhos da Série

Composite + Decorator: O Decorator pode ser aplicado em árvores Composite para adicionar responsabilidades dinamicamente. Por exemplo, adicionar bordas ou sombras a componentes gráficos sem modificar suas classes.

Bridge + Adapter: O Adapter pode adaptar implementações concretas existentes para a interface Implementor do Bridge, permitindo reutilizar código legado.

Bridge + Strategy: Bridge fornece a estrutura estável para que o Strategy varie comportamentos em tempo de execução. A abstração Bridge delega para implementações que podem usar diferentes estratégias algorítmicas.

8. Conclusão e Boas Práticas

Composite e Bridge são padrões complementares que abordam problemas estruturais distintos. Composite oferece composição transparente para hierarquias parte-todo, enquanto Bridge proporciona desacoplamento bidimensional entre abstração e implementação.

Armadilhas comuns:
- Superengenharia em cenários simples: nem toda hierarquia precisa de Composite, nem toda variação precisa de Bridge
- Confusão entre Composite e hierarquias de herança: Composite usa composição, não herança, para estruturar objetos
- Bridge excessivo: aplicar Bridge onde uma única dimensão varia é overengineering

Recomendações finais: Aplique Composite em domínios com árvores naturais (documentos, sistemas de arquivos, interfaces gráficas). Aplique Bridge quando duas dimensões variam independentemente e a explosão combinatória de classes se torna um problema real.

Referências