Adapter
Converte a interface de uma classe em outra interface que os clientes esperam, permitindo que classes trabalhem juntas mesmo com interfaces incompatíveis.
Intenção
Permitir que duas interfaces incompatíveis colaborem envolvendo um objeto com um adaptador que traduz chamadas da interface esperada para a interface real do objeto envolvido.
Problema
Você precisa integrar uma biblioteca de terceiros, um módulo legado, ou um serviço externo cuja interface não corresponde à que sua aplicação espera. Reescrever o código existente é impraticável ou impossível porque você não é dono dele, ou porque muitos outros consumidores já dependem dele.
Solução
Introduza uma classe adaptadora que implementa a interface alvo esperada pelo seu código e internamente delega ao objeto incompatível, traduzindo assinaturas de método, formatos de dados, ou protocolos conforme necessário. O resto da sua aplicação interage apenas com a interface alvo, ficando alheio à adaptação acontecendo por baixo.
Participantes
- Target -- a interface que o cliente espera
- Adaptee -- a classe ou serviço existente com uma interface incompatível
- Adapter -- traduz chamadas da interface Target para o Adaptee
- Client -- colabora com objetos através da interface Target
Vantagens
- Responsabilidade única -- a lógica de tradução vive em um único lugar, separada da lógica de negócio
- Aberto/Fechado -- novos adaptadores podem ser introduzidos sem modificar o código cliente existente ou o adaptee
- Reaproveitamento de código existente e testado que de outra forma não se encaixaria na interface exigida
- Desacopla clientes de detalhes de implementação de terceiros ou legados
Desvantagens
- Adiciona uma camada extra de indireção, que pode obscurecer depuração e stack traces
- Se a interface do adaptee é muito diferente, o adaptador pode ficar complexo e difícil de manter
- Proliferação de classes adaptadoras quando muitas interfaces incompatíveis precisam ser conectadas
Analogia do mundo real
Um adaptador de tomada permite usar um eletrodoméstico europeu em uma tomada americana. O eletrodoméstico (adaptee) tem um plugue de pinos redondos, sua tomada de parede (target) tem entradas chatas, e o adaptador físico traduz entre os dois formatos sem modificar nem o eletrodoméstico nem a tomada.
Casos de uso
- Envolver uma API legada baseada em XML para expor uma interface JSON moderna
- Integrar um gateway de pagamento de terceiros cujo SDK não corresponde à sua interface interna PaymentProcessor
- Conectar um driver de banco de dados antigo a uma nova camada de abstração ORM
- Traduzir entre diferentes frameworks de logging para que o código da aplicação use uma API consistente
- Conectar APIs específicas de plataforma atrás de uma interface unificada multiplataforma
Exemplos de código
interface PaymentProcessor {
PaymentResult charge(double amount, String currency);
}
record PaymentResult(String id, String status) {}
// Adaptee -- legacy third-party SDK with an incompatible interface
final class LegacyXmlPaymentGateway {
String submitPaymentXml(String xml) {
System.out.println("Sending XML: " + xml);
return "<response><txn-id>TXN-42</txn-id><result>OK</result></response>";
}
}
// Adapter translates between the two interfaces
final class XmlPaymentAdapter implements PaymentProcessor {
private final LegacyXmlPaymentGateway gateway;
XmlPaymentAdapter(LegacyXmlPaymentGateway gateway) {
this.gateway = gateway;
}
@Override
public PaymentResult charge(double amount, String currency) {
String xml = "<payment><amount>%.2f</amount><cur>%s</cur></payment>".formatted(amount, currency);
String xmlResponse = gateway.submitPaymentXml(xml);
String id = xmlResponse.replaceAll(".*<txn-id>(.*?)</txn-id>.*", "$1");
String status = xmlResponse.contains("OK") ? "success" : "failed";
return new PaymentResult(id, status);
}
}
public class AdapterDemo {
public static void main(String[] args) {
PaymentProcessor processor = new XmlPaymentAdapter(new LegacyXmlPaymentGateway());
PaymentResult result = processor.charge(49.90, "BRL");
System.out.println(result);
// PaymentResult[id=TXN-42, status=success]
}
}Um adaptador de classe que envolve um gateway de pagamento XML legado atrás de uma interface PaymentProcessor moderna, traduzindo formatos de dados de forma transparente.
Padrões relacionados
Bridge
Desacopla uma abstração da sua implementação, para que as duas possam variar de forma independente.
Decorator
Anexa responsabilidades adicionais a um objeto dinamicamente, oferecendo uma alternativa flexível à subclassificação para estender funcionalidade.