🧱 Capítulo 12: Estruturais II: Composite, Proxy, Bridge e Flyweight
Módulo 10: Paradigmas de Programação & Padrões de Projeto • Bloco 3: Padrões GoF Criacionais & Estruturais
🏛️ Hierarquias em Árvore, Controle de Acesso e Pontes Arquiteturais: Concluindo o estudo dos padrões estruturais do Gang of Four, abordamos quatro ferramentas que resolvem desafios complexos de organização de sistemas: o tratamento uniforme de árvores com Composite, a interceptação e controle de acesso com Proxy, o desacoplamento de duas dimensões ortogonais de variação com Bridge e a otimização massiva de memória com Flyweight.
🗺️ Mapa Conceitual do Capítulo
flowchart TD
GOF_ESTRUT2["Padrões Estruturais GoF (Árvores, Controle & Desacoplamento)"]
GOF_ESTRUT2 --> COMPOSITE["1. Composite (Composto)<br/>• Trata folhas individuais e ramificações compostas de forma uniforme<br/>• Estruturas em árvore recursivas parte-todo (DOM, Pastas)"]
GOF_ESTRUT2 --> PROXY["2. Proxy (Procurador)<br/>• Fornece substituto de mesma interface para controlar o acesso<br/>• Virtual (Lazy), Protection (RBAC), Remote (RPC) e Cache"]
GOF_ESTRUT2 --> BRIDGE["3. Bridge (Ponte)<br/>• Desacopla Abstração de sua Implementação<br/>• Evita explosão cartesiana de subclasses (M x N -> M + N)"]
GOF_ESTRUT2 --> FLYWEIGHT["4. Flyweight (Peso-Mosca)<br/>• Compartilhamento de estado intrínseco imutável<br/>• Redução drástica de consumo de RAM em milhões de instâncias"]
style GOF_ESTRUT2 fill:#ede7f6,stroke:#673ab7,stroke-width:2px
style COMPOSITE fill:#e8f5e9,stroke:#4caf50,stroke-width:1.5px
style PROXY fill:#fff3e0,stroke:#ff9800,stroke-width:1.5px
style BRIDGE fill:#e1f5fe,stroke:#03a9f4,stroke-width:1.5px
style FLYWEIGHT fill:#fce4ec,stroke:#e91e63,stroke-width:1.5px
🌳 1. O Padrão Composite: Hierarquias em Árvore Uniformes
“Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.” — GoF (1994)
1.1 O Desafio da Heterogeneidade em Coleções Aninhadas
Em sistemas de arquivos gráficos ou catálogos comerciais, lidamos com itens simples (um arquivo de texto ou um produto unitário) e recipientes agregadores (um diretório com dezenas de arquivos ou um kit de presentes contendo vários produtos e outros kits menores).
Sem o Composite, o código cliente seria forçado a realizar checagens contínuas com isinstance ou enums de tipo antes de invocar uma operação:
# ANTI-PADRÃO: Código cliente poluído com checagens de tipo em árvore
for item in lista:
if isinstance(item, Arquivo):
tamanho_total += item.obter_tamanho()
elif isinstance(item, Diretorio):
tamanho_total += item.calcular_tamanho_recursivo()
1.2 A Solução Arquitetural do Composite
O padrão define uma interface comum (Component) que tanto as folhas (Leaf) quanto os nós compostos (Composite) implementam. O nó composto armazena uma coleção de filhos e propaga chamadas recursivamente:
classDiagram
class ComponenteCatalogo {
<<interface>>
+obterPreco() float
+imprimir(indentacao) void
}
class ProdutoFolha {
-preco: float
+obterPreco() float
}
class KitComposto {
-filhos: List~ComponenteCatalogo~
+adicionar(ComponenteCatalogo) void
+remover(ComponenteCatalogo) void
+obterPreco() float
}
ComponenteCatalogo <|.. ProdutoFolha
ComponenteCatalogo <|.. KitComposto
KitComposto o--> ComponenteCatalogo : Agrega recursivamente
1.3 O Dilema entre Transparência e Segurança
- Transparência Máxima: Declarar métodos como
adicionar()eremover()diretamente na interfaceComponent. A vantagem é que o cliente trata folhas e nós exatamente da mesma forma. A desvantagem é que invocaradicionar()em uma folha atômica não faz sentido e deve lançar exceção em runtime. - Segurança Estrita de Tipos: Declarar
adicionar()eremover()apenas na classeComposite. A folha não tem métodos inválidos, mas o cliente perde transparência e precisa fazer cast para saber se pode gerenciar filhos.
🛡️ 2. O Padrão Proxy: Interceptação e Controle de Acesso
“Provide a surrogate or placeholder for another object to control access to it.” — GoF (1994)
2.1 Anatomia do Proxy
O Proxy implementa a mesma interface do objeto real de serviço (RealSubject). Para o código cliente, o Proxy é indistinguível do serviço original. No entanto, o Proxy intercepta as mensagens antes de repassá-las ao serviço de destino, executando tarefas de controle:
sequenceDiagram
autonumber
actor Cliente
participant P as RelatorioFinanceiroProxy
participant S as ServicoBancoDadosReal
Cliente->>P: consultarFaturamento(2026)
Note over P: 1. Valida Token JWT e Permissões
Note over P: 2. Verifica se resultado está em Cache
alt Em Cache (Hit)
P-->>Cliente: Retorna dados memorizados (1ms)
else Não está em Cache (Miss)
P->>S: consultarFaturamento(2026)
S-->>P: Dataset pesado do PostgreSQL (450ms)
Note over P: 3. Armazena no Cache Redis
P-->>Cliente: Retorna dataset
end
2.2 As 4 Variantes Primárias do Proxy
- Virtual Proxy (Lazy Loading): Adia a criação dispendiosa de um objeto pesado (como um arquivo de vídeo de alta resolução ou conexão física de socket) até que um método dele seja realmente invocado pela primeira vez.
- Protection Proxy (Segurança e Autorização): Verifica se o usuário autenticado possui as permissões necessárias (RBAC) para executar a operação antes de delegá-la ao objeto real.
- Caching Proxy (Memorização): Armazena os resultados de chamadas caras em memória e os devolve imediatamente quando os mesmos parâmetros forem repetidos.
- Remote Proxy (RPC / Microserviços): Representa localmente um objeto que existe fisicamente em outra máquina ou contêiner de rede, serializando chamadas via gRPC ou HTTP sem que o cliente perceba a barreira da rede.
🌉 3. O Padrão Bridge: Desacoplando Dimensões Ortogonais
“Decouple an abstraction from its implementation so that the two can vary independently.” — GoF (1994)
3.1 O Problema da Explosão Cartesiana de Subclasses
Considere uma aplicação que modela dispositivos eletrônicos (ControleRemoto) que precisam operar aparelhos de televisão de diferentes marcas (Sony, Samsung, LG).
Se tentarmos resolver as variações usando herança direta:
ControleBasicoSony, ControleBasicoSamsung, ControleAvancadoSony, ControleAvancadoSamsung…
Para $M$ variações de controle e $N$ fabricantes de TV, seremos forçados a criar $M \times N$ subclasses. Se quisermos adicionar suporte a um novo fabricante, precisamos criar subclasses para cada tipo de controle remoto existente!
3.2 A Solução Bridge ($M + N$)
O Bridge divide o problema em duas hierarquias separadas conectadas por uma “ponte” de composição:
- Hierarquia da Abstração: Modela a lógica de alto nível (
ControleRemoto,ControleRemotoAvancado). - Hierarquia do Implementador: Modela as operações primitivas específicas de plataforma (
IDispositivoTVcomligar(),desligar(),definirCanal()).
classDiagram
class ControleRemoto {
-dispositivo: IDispositivoTV
+ligarDesligar() void
+trocarCanal(canal: int) void
}
class ControleAvancado {
+modoMudo() void
}
class IDispositivoTV {
<<interface>>
+habilitar() void
+desabilitar() void
+ajustarCanal(c: int) void
}
class SonyTV {
+habilitar() void
+desabilitar() void
+ajustarCanal(c: int) void
}
class SamsungTV {
+habilitar() void
+desabilitar() void
+ajustarCanal(c: int) void
}
ControleRemoto <|-- ControleAvancado
ControleRemoto o--> IDispositivoTV : Bridge (Ponte)
IDispositivoTV <|.. SonyTV
IDispositivoTV <|.. SamsungTV
Agora, adicionar um novo fabricante exige apenas $1$ nova classe; adicionar um novo tipo de controle exige $1$ nova classe. O crescimento passa de multiplicativo ($M \times N$) para linear ($M + N$).
🪰 4. O Padrão Flyweight: Compartilhamento Eficiente de Estado
“Use sharing to support large numbers of fine-grained objects efficiently.” — GoF (1994)
Em sistemas gráficos, simulações ou renderização de texto, criar milhões de instâncias completas de objetos esgota a memória RAM. O Flyweight divide o estado do objeto em duas categorias:
- Estado Intrínseco (Intrinsic State): Compartilhado, invariável e imutável entre todas as instâncias (ex.: o glifo de uma letra, a cor da textura 3D de uma árvore).
- Estado Extrínseco (Extrinsic State): Varia de acordo com o contexto específico e é passado pelo cliente como argumento no momento da invocação do método (ex.: as coordenadas
(x, y)onde a letra ou árvore deve ser desenhada).
Uma Flyweight Factory garante que objetos de estado intrínseco nunca sejam duplicados na memória, reaproveitando a mesma instância em milhares de pontos de renderização.
📊 5. Matriz Comparativa Sistemática: Quatro Padrões Estruturais
| Padrão GoF | Propósito Primário | Relação de Interface | Ponto Crítico de Atenção |
|---|---|---|---|
| Composite | Estruturas recursivas árvore parte-todo | Mesma interface para folhas e nós compostos | Cuidado com laços cíclicos na árvore |
| Proxy | Interceptar e controlar o acesso a outro objeto | Idêntica à interface do serviço real alvo | Evitar atrasos adicionais (overhead) excessivos |
| Bridge | Separar duas dimensões de variação independentes | Abstração consome interface do Implementador | Exige planejamento antecipado das duas hierarquias |
| Flyweight | Economizar memória compartilhando instâncias | Objetos representam apenas estado intrínseco | O estado intrínseco compartilhado DEVE ser imutável |