Pular para o conteúdo

O Adapter é um padrão de projeto estrutural que permite que objetos com interfaces incompatíveis colaborem entre si. Em outras palavras, ele funciona como uma ponte de tradução entre duas partes que não conseguem se comunicar diretamente.

Esse padrão costuma aparecer quando um sistema já está funcionando com uma interface esperada, mas surge a necessidade de integrar uma biblioteca externa, um serviço legado ou uma API com formato diferente. O Adapter existe para evitar mudanças desnecessárias no cliente e reaproveitar componentes úteis sem reescrever tudo.

Imagine um sistema web de monitoramento financeiro que recebe dados em XML. Depois de algum tempo, a equipe decide integrar uma biblioteca de análise de mercado de terceiros, mas essa biblioteca só aceita dados em JSON.

Sem um padrão adequado, surgem problemas práticos:

  • o código cliente passa a conhecer detalhes da biblioteca externa
  • a conversão de XML para JSON fica espalhada pelo sistema
  • qualquer mudança no fornecedor impacta vários pontos do código
  • o acoplamento com a interface antiga aumenta
  • reutilizar componentes externos fica mais difícil do que deveria

Além disso, muitas vezes a biblioteca externa não pode ser modificada, seja por ser de terceiros, seja por já estar em produção em outros sistemas.

O Adapter resolve esse problema criando um objeto intermediário que converte uma interface em outra.

  1. O cliente continua falando com a interface que já conhece.
  2. O adaptador implementa essa interface esperada pelo cliente.
  3. Internamente, o adaptador recebe a chamada e a traduz.
  4. Depois, ele delega a execução para o serviço real, no formato que ele entende.
  5. O cliente passa a colaborar com o serviço incompatível sem conhecer seus detalhes internos.

Na prática, o Adapter diz: “não vou mudar o cliente nem o serviço; vou apenas traduzir a conversa entre eles”.

Pense em um carregador de notebook brasileiro sendo usado em uma tomada europeia. O notebook espera um encaixe de um tipo, enquanto a tomada oferece outro. Em vez de trocar o notebook ou reconstruir a instalação elétrica do prédio, usamos um adaptador de tomada.

O adaptador não muda nem o notebook nem a tomada. Ele apenas converte a interface física para que os dois possam funcionar juntos.

Use Adapter quando:

  • você precisa integrar código legado com código novo
  • uma biblioteca externa possui interface incompatível com o sistema atual
  • você quer reaproveitar uma classe útil sem alterar seu contrato original
  • diferentes formatos de dados precisam conversar entre si
  • deseja isolar a conversão em um ponto central

Exemplos reais em software moderno:

  • integração de APIs REST que retornam formatos diferentes
  • adaptação de gateways de pagamento em e-commerces
  • uso de SDKs de terceiros em sistemas internos
  • compatibilização entre DTOs antigos e novos em sistemas web
  • wrappers para bibliotecas de autenticação, armazenamento ou mensageria

Adapter pode ser mal utilizado quando passa a esconder problemas maiores de modelagem. Alguns erros comuns são:

  • usar adaptadores em excesso para corrigir interfaces mal projetadas internamente
  • misturar lógica de negócio com lógica de conversão
  • criar adaptadores enormes, que fazem muito mais do que traduzir chamadas
  • usar Adapter quando seria mais simples refatorar o contrato original

As consequências práticas incluem aumento de complexidade, dificuldade de manutenção e a criação de camadas artificiais que confundem quem lê o código.

Um bom Adapter deve ser enxuto, claro e focado em compatibilidade, não em acumular responsabilidades extras.

Uma forma incremental de implementar Adapter é:

  1. Identifique a interface que o cliente espera.
  2. Identifique a classe ou serviço incompatível que precisa ser reutilizado.
  3. Crie uma classe adaptadora que implemente a interface esperada.
  4. Faça o adaptador armazenar uma referência para o serviço real.
  5. Traduza chamadas, parâmetros ou formatos dentro do adaptador.
  6. Faça o cliente depender apenas da abstração esperada.

Antes de escrever o código, vale responder: o adaptador vai traduzir apenas métodos ou também dados, formatos e convenções? Isso ajuda a definir seu tamanho e responsabilidade.

No exemplo abaixo, um sistema de relatórios espera trabalhar com um provedor JSON, mas a biblioteca legada disponível entrega XML.

interface ProvedorRelatorio {
String obterRelatorioEmJson();
}
class SistemaLegadoXml {
public String obterRelatorioEmXml() {
return "<relatorio><total>150</total></relatorio>";
}
}
class XmlParaJsonAdapter implements ProvedorRelatorio {
private SistemaLegadoXml sistemaLegado;
public XmlParaJsonAdapter(SistemaLegadoXml sistemaLegado) {
this.sistemaLegado = sistemaLegado;
}
@Override
public String obterRelatorioEmJson() {
String xml = sistemaLegado.obterRelatorioEmXml();
return converterXmlParaJson(xml);
}
private String converterXmlParaJson(String xml) {
return "{ \"total\": 150 }";
}
}
public class App {
public static void main(String[] args) {
ProvedorRelatorio provedor = new XmlParaJsonAdapter(new SistemaLegadoXml());
System.out.println(provedor.obterRelatorioEmJson());
}
}

Nesse código, o cliente depende apenas de ProvedorRelatorio. Já o adaptador XmlParaJsonAdapter conhece o sistema legado, traduz a resposta e entrega o formato esperado.

O ponto principal é que o cliente continua simples, enquanto a conversão fica isolada em um lugar previsível.

  • reaproveita classes existentes sem alterar seu código
  • reduz acoplamento entre cliente e serviço incompatível
  • centraliza regras de conversão
  • facilita integração com bibliotecas externas e sistemas legados
  • ajuda a aplicar o princípio aberto/fechado
  • adiciona mais classes e camadas ao sistema
  • pode aumentar a complexidade se usado em excesso
  • não corrige um design ruim, apenas adapta interfaces
  • adaptadores mal feitos podem acumular responsabilidades demais

O Adapter se relaciona com outros padrões importantes:

  • Facade: ambos encapsulam complexidade, mas Facade simplifica um subsistema; Adapter traduz interfaces incompatíveis
  • Decorator: Decorator adiciona comportamento mantendo a mesma interface; Adapter muda a interface percebida pelo cliente
  • Proxy: Proxy controla acesso mantendo a mesma interface; Adapter altera a interface para torná-la compatível
  • Bridge: Bridge é pensado desde o projeto para separar abstrações e implementações; Adapter geralmente aparece depois, para integrar o que já existe

Essas comparações ajudam a entender que Adapter não existe para “melhorar” uma interface, mas para torná-la utilizável em outro contexto.

As formas mais comuns de implementar Adapter são:

  • adaptador por objeto, usando composição
  • adaptador por classe, usando herança quando a linguagem permite
  • wrappers de API, comuns em integrações com serviços externos
  • adaptadores bidirecionais, quando a conversão precisa acontecer nos dois sentidos

Na prática, o adaptador por objeto costuma ser o mais usado em Java, porque é mais flexível e evita limitações de herança.

Agora veja um caso mais realista em um sistema acadêmico. O portal da universidade espera um serviço de notificações padronizado, mas a biblioteca legada disponível envia mensagens com outra interface.

interface Notificador {
void enviar(String destinatario, String mensagem);
}
class ServicoLegadoSms {
public void sendSms(String numero, String texto) {
System.out.println("SMS enviado para " + numero + ": " + texto);
}
}
class SmsAdapter implements Notificador {
private ServicoLegadoSms servicoLegadoSms;
public SmsAdapter(ServicoLegadoSms servicoLegadoSms) {
this.servicoLegadoSms = servicoLegadoSms;
}
@Override
public void enviar(String destinatario, String mensagem) {
String numeroFormatado = formatarNumero(destinatario);
servicoLegadoSms.sendSms(numeroFormatado, mensagem);
}
private String formatarNumero(String destinatario) {
return "+55" + destinatario;
}
}
class NotificadorTurma {
private Notificador notificador;
public NotificadorTurma(Notificador notificador) {
this.notificador = notificador;
}
public void avisarInicioDasAulas() {
notificador.enviar("48999999999", "As aulas iniciam na proxima semana.");
}
}
public class UniversidadeApp {
public static void main(String[] args) {
Notificador notificador = new SmsAdapter(new ServicoLegadoSms());
NotificadorTurma notificadorTurma = new NotificadorTurma(notificador);
notificadorTurma.avisarInicioDasAulas();
}
}

Nesse cenário, NotificadorTurma trabalha apenas com a interface Notificador. O adaptador SmsAdapter recebe a chamada, ajusta o formato esperado e delega para ServicoLegadoSms.

Isso permite substituir o serviço legado no futuro ou criar outros adaptadores, como EmailAdapter ou PushAdapter, sem alterar a lógica principal do sistema.