Padrões estruturais: Adapter e Facade

1. Introdução aos Padrões Estruturais

Os padrões estruturais são um dos pilares da arquitetura de software, responsáveis por organizar classes e objetos em estruturas maiores, garantindo flexibilidade e reutilização. Diferentemente dos padrões criacionais, que tratam da instanciação de objetos, e dos comportamentais, que focam na comunicação entre entidades, os padrões estruturais lidam com a composição de interfaces e a forma como os componentes se relacionam.

Dentro desse grupo, dois padrões se destacam por resolverem problemas complementares: o Adapter, que permite que interfaces incompatíveis trabalhem juntas, e o Facade, que simplifica o acesso a subsistemas complexos. Ambos são essenciais para arquitetos que buscam sistemas modulares e de fácil manutenção.

2. Padrão Adapter: Conceito e Fundamentos

O Adapter resolve um dos problemas mais comuns em sistemas de software: a incompatibilidade entre interfaces. Imagine que você precisa integrar um sistema legado de pagamentos com uma API moderna de e-commerce. O sistema antigo espera chamadas no formato XML, enquanto a nova API exige JSON. Sem um adaptador, seria necessário reescrever todo o código legado.

A analogia clássica é o adaptador de tomada elétrica: um dispositivo que converte um plugue de formato diferente para que ele se encaixe em uma tomada específica. No software, o padrão atua da mesma forma, convertendo a interface de uma classe (Adaptee) em outra interface esperada pelo cliente (Target).

A estrutura básica envolve quatro elementos:
- Target: a interface que o cliente espera.
- Adaptee: a classe existente com interface incompatível.
- Adapter: a classe que converte a interface do Adaptee para o Target.
- Client: o código que utiliza o Target.

3. Implementação do Adapter: Abordagens e Exemplos

Existem duas abordagens principais para implementar o Adapter: por herança (Adapter de classe) e por composição (Adapter de objeto). A primeira utiliza herança múltipla (em linguagens que suportam) e é mais restrita; a segunda, mais flexível, usa composição e é preferida na maioria dos cenários.

Considere um sistema legado de pagamentos que processa transações via SOAP:

// Interface esperada pelo novo sistema (Target)
public interface PaymentProcessor {
    void processPayment(double amount);
}

// Sistema legado (Adaptee)
public class LegacyPaymentSystem {
    public void makeTransaction(String xmlData) {
        // Lógica legada em SOAP
    }
}

// Adapter usando composição
public class PaymentAdapter implements PaymentProcessor {
    private LegacyPaymentSystem legacySystem;

    public PaymentAdapter(LegacyPaymentSystem legacy) {
        this.legacySystem = legacy;
    }

    @Override
    public void processPayment(double amount) {
        String xml = convertToXML(amount);
        legacySystem.makeTransaction(xml);
    }

    private String convertToXML(double amount) {
        return "<payment><amount>" + amount + "</amount></payment>";
    }
}

// Cliente
public class ECommerceApp {
    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentAdapter(new LegacyPaymentSystem());
        processor.processPayment(150.0);
    }
}

4. Variações e Aplicações Avançadas do Adapter

O Adapter pode ser bidirecional, quando ambos os lados precisam de adaptação simultânea. Por exemplo, em uma migração gradual de sistemas, onde o novo sistema precisa conversar com o antigo e vice-versa.

Em arquiteturas de microserviços, o Adapter é frequentemente usado para adaptar protocolos de comunicação. Um serviço que espera gRPC pode receber chamadas REST através de um adaptador que traduz as requisições. Isso é comum em cenários de integração com sistemas legados que só expõem SOAP.

Importante diferenciar o Adapter do Proxy (já abordado na série): enquanto o Proxy controla o acesso a um objeto (adiando criação, adicionando segurança), o Adapter foca exclusivamente na conversão de interfaces. Eles podem ser combinados, mas têm propósitos distintos.

5. Padrão Facade: Conceito e Fundamentos

O Facade resolve o problema da complexidade excessiva de subsistemas interdependentes. Em sistemas grandes, como um e-commerce, o cliente pode precisar interagir com módulos de estoque, pagamento, envio e notificação. Sem uma fachada, o código cliente se torna acoplado a todas essas dependências.

A analogia é a fachada de um prédio: ela esconde a infraestrutura interna (canos, fiação, elevadores) e oferece uma interface simples para o visitante. No software, o Facade faz o mesmo, fornecendo um ponto único de entrada para funcionalidades complexas.

A estrutura é composta por:
- Facade: classe que oferece métodos simplificados.
- Subsistemas: classes complexas que realizam o trabalho real.
- Client: código que usa a Facade.

6. Implementação do Facade: Casos de Uso e Exemplos

Imagine um sistema de e-commerce com os subsistemas Estoque, Pagamento e Envio. Sem uma fachada, o cliente precisaria chamar cada um separadamente:

// Subsistemas complexos
public class InventoryService {
    public boolean checkStock(String productId) { /* ... */ return true; }
    public void reserveItem(String productId) { /* ... */ }
}

public class PaymentService {
    public boolean processPayment(String userId, double amount) { /* ... */ return true; }
}

public class ShippingService {
    public String scheduleDelivery(String address, String productId) { /* ... */ return "TRACK123"; }
}

// Facade simplificada
public class OrderFacade {
    private InventoryService inventory;
    private PaymentService payment;
    private ShippingService shipping;

    public OrderFacade() {
        this.inventory = new InventoryService();
        this.payment = new PaymentService();
        this.shipping = new ShippingService();
    }

    public String placeOrder(String userId, String productId, String address, double amount) {
        if (!inventory.checkStock(productId)) {
            throw new RuntimeException("Estoque insuficiente");
        }
        inventory.reserveItem(productId);
        if (!payment.processPayment(userId, amount)) {
            throw new RuntimeException("Pagamento recusado");
        }
        return shipping.scheduleDelivery(address, productId);
    }
}

// Cliente simplificado
public class Client {
    public static void main(String[] args) {
        OrderFacade facade = new OrderFacade();
        String tracking = facade.placeOrder("user123", "PROD456", "Rua A, 100", 250.0);
        System.out.println("Pedido realizado. Código: " + tracking);
    }
}

Os benefícios são claros: redução de acoplamento, ponto único de entrada para manutenção e maior testabilidade (a fachada pode ser mockada).

7. Comparação e Relacionamento entre Adapter e Facade

Embora ambos sejam padrões estruturais, suas finalidades são distintas:

  • Adapter: converte uma interface em outra para compatibilidade. Foco em integração.
  • Facade: simplifica o acesso a um conjunto de interfaces. Foco em redução de complexidade.

O Adapter é usado quando você já tem um sistema funcionando, mas precisa que ele se encaixe em um novo contexto. O Facade é usado quando você quer esconder a complexidade de um subsistema do cliente.

Eles podem ser combinados: uma Facade pode usar Adapters internamente para integrar sistemas legados. Por exemplo, a fachada de um sistema de BI pode usar adaptadores para conectar-se a bancos de dados relacionais e NoSQL, oferecendo uma interface unificada de consulta.

8. Boas Práticas e Considerações Arquiteturais

Ao aplicar esses padrões, algumas práticas são importantes:

  • Coesão: a fachada não deve virar um "deus objeto" que faz tudo. Mantenha-a focada em um domínio específico.
  • Acoplamento: adaptadores devem ser leves e focados apenas na conversão. Evite lógica de negócio dentro deles.
  • Testabilidade: ambos os padrões facilitam testes, pois isolam dependências. Use injeção de dependência para maior flexibilidade.

Armadilhas comuns incluem fachadas inchadas (que tentam cobrir todo o sistema) e adaptadores excessivos (que criam camadas desnecessárias). Avalie sempre se o padrão realmente simplifica o design.

Em relação aos próximos temas da série (Composite e Bridge), o Adapter e o Facade estabelecem a base para entender composição de objetos e abstração de interfaces, conceitos que serão aprofundados.

Referências