Padrões Estruturais: Adapter, Decorator e Facade em Arquiteturas Desacopladas
Como compor interfaces, estender comportamentos dinamicamente e simplificar subsistemas complexos utilizando os padrões estruturais clássicos do GoF.
Neste artigo
À medida que os sistemas corporativos evoluem, a quantidade de classes, subsistemas internos e bibliotecas legadas de terceiros tende a se multiplicar exponencialmente. Nesse cenário de crescimento contínuo, herança tradicional entre classes rapidamente revela suas limitações: tentar estender comportamentos criando árvores profundas de subclasses gera código rígido, explosão de classes e fragilidade estrutural.
Os Padrões Estruturais do catálogo Gang of Four (GoF) lidam exatamente com a maneira como classes e objetos são compostos para formar estruturas maiores, coesas e flexíveis. Em vez de depender do acoplamento estático da herança, esses padrões priorizam a composição em tempo de execução, permitindo que subsistemas complexos colaborem harmoniosamente sem exigir refatorações destrutivas.
O Papel dos Padrões Estruturais na Arquitetura
| Padrão Estrutural | Intenção Principal | Aplicação Típica |
|---|---|---|
| Adapter | Converte a interface de uma classe em outra esperada pelos clientes. | Integração de bibliotecas externas ou sistemas legados com contratos incompatíveis. |
| Decorator | Anexa novas responsabilidades a um objeto de forma dinâmica e transparente. | Adição de logging, cache, métricas ou criptografia sem alterar a classe original. |
| Facade | Fornece uma interface unificada e simplificada para um subsistema complexo. | Coordenação de fluxos compostos (como checkout) reduzindo o acoplamento do cliente. |
| Composite | Compõe objetos em estruturas de árvore para representar hierarquias todo-parte. | Manipulação uniforme de elementos individuais e coleções de elementos compostos. |
| Proxy | Fornece um substituto ou intermediário para controlar o acesso a outro objeto. | Controle de acesso sob demanda, lazy loading, autorização ou proxies remotos. |
Entre os padrões estruturais, Adapter, Decorator e Facade são as ferramentas mais poderosas para manter arquiteturas limpas diante da complexidade do mundo real.
1. Adapter: A Ponte para Interfaces Incompatíveis
O padrão Adapter atua exatamente como um adaptador de tomada universal: ele traduz a interface de uma classe para outra interface que o código cliente já conhece e sabe consumir. É o mecanismo predileto para integrar bibliotecas externas ou sistemas legados sem deixar que seus tipos específicos vazem para a camada de domínio da aplicação.
Exemplo Prático: Integrando um Provedor de Mensageria Legado
Suponha que nossa aplicação utilize a interface limpa IMessageSender. Ao integrar um SDK legado de mensageria que expõe métodos assíncronos não padronizados e classes proprietárias, construímos um Adapter para compatibilizar o contrato:
// Contrato esperado pela nossa aplicação
public interface IMessageSender
{
Task SendAsync(string destination, string content);
}
// SDK Legado incompatível de terceiros
public class LegacyXmlBusService
{
public void TransmitRawBuffer(byte[] payload, string targetTopic) { /* ... */ }
}
// O Adaptador
public class LegacyBusAdapter : IMessageSender
{
private readonly LegacyXmlBusService _legacyService;
public LegacyBusAdapter(LegacyXmlBusService legacyService)
{
_legacyService = legacyService;
}
public Task SendAsync(string destination, string content)
{
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(content);
_legacyService.TransmitRawBuffer(buffer, destination);
return Task.CompletedTask;
}
}
A aplicação continua interagindo com IMessageSender, totalmente alheia às peculiaridades do serviço legado.
2. Decorator: Enriquecimento Dinâmico sem Herança
O padrão Decorator permite anexar responsabilidades adicionais a um objeto dinamicamente, sem alterar a classe base e sem criar subclasses infinitas. Ele opera envelopando a instância alvo em uma nova classe que implementa a mesma interface, interceptando chamadas para executar ações antes ou depois de repassá-las ao objeto envelopado.
A Explosão de Subclasses versus o Empilhamento de Decorators
Se você tiver um serviço OrderRepository e precisar adicionar Logging, Caching e Métricas de Telemetria, a herança forçaria a criação de classes como CachedOrderRepository, LoggedOrderRepository e a combinação incontrolável CachedAndLoggedOrderRepository.
Com Decorators, criamos wrappers desacoplados que podem ser empilhados conforme a necessidade:
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(Guid id);
}
// Decorator de Caching
public class CachedOrderRepository : IOrderRepository
{
private readonly IOrderRepository _inner;
private readonly IDistributedCache _cache;
public CachedOrderRepository(IOrderRepository inner, IDistributedCache cache)
{
_inner = inner;
_cache = cache;
}
public async Task<Order?> GetByIdAsync(Guid id)
{
var cached = await _cache.GetAsync<Order>(id.ToString());
if (cached != null) return cached;
var order = await _inner.GetByIdAsync(id);
if (order != null)
await _cache.SetAsync(id.ToString(), order, TimeSpan.FromMinutes(10));
return order;
}
}
Podemos empilhar: Logging(Caching(SqlOrderRepository)), alcançando total modularidade e conformidade absoluta com o Princípio Aberto/Fechado (OCP).
3. Facade: Simplificando Subsistemas Labirínticos
O padrão Facade (Fachada) fornece uma interface de alto nível simples e unificada para um conjunto complexo de classes, subsistemas ou micro-operações. Ele não adiciona novos comportamentos e não oculta os subsistemas para quem precisar de configurações avançadas; sua função é fornecer um ponto de entrada intuitivo para os 90% dos casos comuns de uso.
Cenário de Aplicação: Fluxo de Finalização de Compra (Checkout)
Um fluxo de checkout corporativo envolve orquestrar diversos módulos independentes:
- Validar disponibilidade no estoque (
InventoryService) - Efetuar cobrança no cartão (
PaymentGateway) - Emitir nota fiscal eletrônica (
InvoiceService) - Gerar etiqueta de rastreio de logística (
ShippingService) - Disparar e-mail de confirmação (
NotificationService)
Ouvir o cliente da API ter que coordenar essas cinco chamadas é uma fonte contínua de erros de estado inconsistente. A Facade unifica toda essa coreografia sob uma única chamada limpa:
public class CheckoutFacade
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IInvoiceService _invoice;
private readonly IShippingService _shipping;
private readonly INotificationService _notifications;
public async Task<CheckoutResult> CompleteCheckoutAsync(Cart cart, PaymentDetails payment)
{
await _inventory.ReserveItemsAsync(cart.Items);
var paymentResult = await _payment.ChargeAsync(payment, cart.Total);
var invoice = await _invoice.GenerateAsync(cart, paymentResult);
var trackingCode = await _shipping.SchedulePickupAsync(cart.Items, cart.ShippingAddress);
await _notifications.SendOrderConfirmationAsync(cart.CustomerEmail, trackingCode);
return new CheckoutResult(paymentResult.TransactionId, trackingCode);
}
}
Comparativo Estrutural: Adapter vs Decorator vs Facade
| Critério | Adapter | Decorator | Facade |
|---|---|---|---|
| Intenção Principal | Converter interfaces incompatíveis | Adicionar responsabilidades dinamicamente | Simplificar o acesso a subsistemas |
| Interface Resultante | Interface nova/diferente | Exatamente a mesma interface original | Nova interface de alto nível |
| Alvo Principal | Um objeto existente com interface diferente | Um objeto que necessita de enriquecimento | Múltiplos objetos e subsistemas interdependentes |
| Ponto Forte | Reaproveita código legado sem quebras | Substitui herança profunda com elegância | Reduz acoplamento e complexidade de integração |
Aplicação Prática de Padrões Estruturais na Arquitetura
Em arquitetura de software orientada a manutenibilidade, esses três padrões são empregados para preservar as fronteiras entre módulos:
- Adapters como Camada Anticorrupção (ACL): Sempre que conectamos sistemas legados ou ERPs corporativos às nossas aplicações modernas, construímos Adapters dedicados, impedindo que tipos do banco legado poluam o modelo de domínio do produto.
- Decorators para Cross-Cutting Concerns: Implementamos logging estruturado, resiliência (Polly), caching e validação de segurança via Decorators registrados no pipeline de injeção de dependência (
Scrutorno .NET), mantendo os serviços de domínio focados exclusivamente em lógica de negócio. - Facades para Coordenação de Domínios: Em vez de expor múltiplos serviços interdependentes para controladores de API ou frontends, criamos Facades de aplicação coesas que coordenam as transações e garantem a consistência do fluxo.