Software · Design Patterns

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.

Diagrama conceitual de padrões estruturais GoF ilustrando Adapter compatibilizando interfaces Decorator empilhando responsabilidades e Facade simplificando subsistemas
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:

  1. Validar disponibilidade no estoque (InventoryService)
  2. Efetuar cobrança no cartão (PaymentGateway)
  3. Emitir nota fiscal eletrônica (InvoiceService)
  4. Gerar etiqueta de rastreio de logística (ShippingService)
  5. 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:

  1. 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.
  2. 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 (Scrutor no .NET), mantendo os serviços de domínio focados exclusivamente em lógica de negócio.
  3. 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.
Explorar este tema no Grafo de Conhecimento