EstruturalIniciante5 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Adapter

Converte a interface de uma classe em outra que o cliente espera, permitindo que classes incompatíveis trabalhem juntas.

Em 10 segundos

Quando uma classe útil fala uma 'língua' diferente da que seu código espera, o Adapter é o tradutor: implementa a interface que o cliente conhece e, por dentro, converte cada chamada para o objeto incompatível.

  • #gof
  • #integracao
  • #wrapper
  • #compatibilidade
Analogia

O adaptador de tomada de viagem

Seu notebook tem plug brasileiro; a tomada do hotel em Londres tem três pinos retangulares. Você não refaz o cabo do notebook nem a parede do hotel — encaixa um adaptador de viagem entre os dois. O notebook continua 'achando' que está numa tomada normal, a parede continua recebendo o plug que espera, e a energia passa. O Adapter faz exatamente isso com interfaces de código: ninguém muda, alguém traduz.

01Conceito

O problema

Você encontrou a classe perfeita — um SDK de e-mail, um client de pagamento, uma lib de datas — mas a assinatura dela não bate com a interface que o seu domínio já usa. O compilador não aceita, e o encaixe direto não existe.

Reescrever a biblioteca não é opção (é de terceiros); mudar todos os pontos do seu código que dependem da interface atual é caro e arriscado.

A raiz do problema é que interfaces são contratos rígidos por design: é justamente essa rigidez que dá segurança de tipos. O preço aparece na fronteira entre códigos que evoluíram separados — seu domínio modelou 'enviar(mensagem)' pensando no negócio, o SDK expôs 'dispatch(payload)' pensando na generalidade dele. Nenhum dos dois está errado; eles só nunca se conheceram.

A tentação ingênua é 'deixar o SDK vazar': fazer o domínio inteiro passar a falar dispatch/payload. Funciona hoje, mas acopla todas as camadas ao vocabulário de um fornecedor — e quando o fornecedor mudar (ou você trocar de fornecedor), a mudança se espalha por todo lugar. O Adapter inverte isso: o vocabulário do domínio manda, e a tradução fica concentrada em um único arquivo.

Vale distinguir do Facade e do Decorator, que também 'embrulham' objetos: o Facade simplifica um subsistema inteiro criando uma interface nova; o Decorator mantém a mesma interface e adiciona comportamento. O Adapter não simplifica nem adiciona — converte uma interface existente em outra existente, sem mudar a semântica.

Como se encaixa
  1. Cliente
  2. chama enviar()Interface Alvo
  3. implementaAdaptador
  4. traduz p/ dispatch()SDK incompatível
O cliente só conhece a interface alvo; a tradução para o mundo incompatível acontece em um único ponto — o adaptador.
02Interativo

Ligue o adaptador e veja a tradução

Mexa nos controles: a teoria acima vira comportamento observável.

Cliente

enviar("Olá")

SDK externo

dispatch({ body: "Olá" })
Interface incompatível: o cliente não fala a língua do SDK
03Anatomia

Navegue pelas camadas

Camada 1/4Cliente

Camada 1

Cliente

usa o contrato do domínio, sem saber quem o cumpre

É qualquer código seu que precisa do serviço — um caso de uso, um controller. Ele depende apenas da interface alvo e recebe a implementação de fora (injeção). Nunca importa nem menciona o SDK.

No código

const n: Notificador = new EmailAdapter(new SdkEmail());
n.enviar("Olá");

Se violar: se o cliente importar o SDK direto, o acoplamento se espalha: trocar de fornecedor passa a exigir mudanças em todos os pontos de uso.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// Interface que o domínio espera
interface Notificador {
  enviar(mensagem: string): void;
}

// SDK externo com outra assinatura
class SdkEmail {
  dispatch(payload: { body: string }) { /* ... */ }
}

// Adapter
class EmailAdapter implements Notificador {
  constructor(private sdk: SdkEmail) {}
  enviar(mensagem: string) {
    this.sdk.dispatch({ body: mensagem });   // traduz
  }
}

const n: Notificador = new EmailAdapter(new SdkEmail());
n.enviar("Olá");
06No mundo real

Casos de uso

Gateway de pagamento múltiplo

Cenário

Um e-commerce precisa aceitar Stripe, PagSeguro e Pix — cada um com SDK, nomes e formatos de resposta completamente diferentes.

Como o padrão entra

O domínio define uma interface única (cobrar, estornar, consultar) e cada provedor ganha seu adaptador. O checkout não sabe qual gateway está ativo; adicionar um quarto provedor é escrever mais um adaptador, sem tocar no fluxo.

Trade-off: A interface comum tende ao mínimo denominador: recursos exclusivos de um provedor (parcelamento especial, antifraude próprio) ficam difíceis de expor sem vazar detalhes.

Migração de SDK sem big-bang

Cenário

O fornecedor descontinuou a v1 do SDK e a v2 mudou todas as assinaturas. Há dezenas de pontos de uso no sistema e ninguém quer uma migração de semanas em uma branch gigante.

Como o padrão entra

Escreve-se um adaptador que expõe a interface antiga por cima do SDK novo. O sistema inteiro migra em um commit (troca a implementação injetada) e os pontos de uso são modernizados aos poucos, com o adaptador encolhendo até sumir.

Trade-off: O adaptador é código temporário que vira permanente se ninguém terminar a migração — precisa de dono e prazo, senão vira camada fóssil.

Camada anticorrupção contra API legada

Cenário

Um sistema novo precisa consumir um ERP legado cheio de nomes crípticos, códigos mágicos e campos reaproveitados (o clássico 'campo observação que guarda três coisas').

Como o padrão entra

Adaptadores formam a fronteira anticorrupção (DDD): traduzem os conceitos tortos do legado para o modelo limpo do sistema novo. A sujeira fica confinada na borda; o domínio novo nunca vê um código mágico.

Trade-off: A tradução tem custo real de manutenção — cada mudança do legado exige atualizar o adaptador — e mapeamentos semânticos errados na borda corrompem silenciosamente o modelo novo.

Passo a passoComo adaptar sem contaminar
  1. 1

    Fixar a interface alvo

    Escreva o contrato no vocabulário do domínio (`enviar(mensagem)`), sem nenhum tipo do SDK. Esse é o lado estável.

  2. 2

    Embrulhar o incompatível

    O adaptador implementa o alvo e recebe o SDK por construtor. Por dentro, traduz nomes, formatos e erros.

  3. 3

    Injetar no cliente

    Quem usa depende só da interface alvo. Trocar de fornecedor vira trocar o adaptador injetado — zero diff nos casos de uso.

  4. 4

    Manter o adaptador fino

    Só tradução. Regra de negócio aqui some quando o fornecedor muda — e a decisão precisa viver no domínio.

O Adapter que vaza o SDK

Compila, roda, passa no code review. E quebra depois.

Existe uma classe chamada Adapter e uma interface alvo — mas a assinatura carrega SdkPayload e SdkReceipt. O tradutor virou repasse; o domínio continua falando a língua do fornecedor.

// "Temos Adapter"... e os tipos do SDK vazaram pela interface alvo.
import type { SdkPayload, SdkReceipt } from "sdk-email";

interface Notificador {
  enviar(payload: SdkPayload): SdkReceipt; // <- dominio fala SDK
}

class EmailAdapter implements Notificador {
  constructor(private sdk: SdkEmail) {}
  enviar(payload: SdkPayload) {
    return this.sdk.dispatch(payload); // repasse, nao traducao
  }
}

// Todo cliente importa SdkPayload. Trocar de fornecedor
// espalha mudanca pelo sistema inteiro — o adaptador nao isolou nada.

O que quebra

  • Ao trocar de fornecedorTodos os clientes que montam SdkPayload precisam mudar — o isolamento era só de nome.
  • No importArquivos de domínio importam tipos do SDK; a fronteira anticorrupção não existe.
  • No testeO falso precisa fabricar objetos do fornecedor real, em vez de mensagens do domínio.

A saída: A interface alvo usa apenas tipos seus: enviar(mensagem: string): void. Conversão completa — ida e volta, inclusive de erros — é o trabalho do adaptador.

08Cuidado

Armadilhas

  1. Deixar o tipo do SDK vazar pela interface

    Se enviar() recebe ou retorna um tipo do fornecedor (SdkPayload, StripeCharge), o adaptador não isolou nada: todo cliente continua acoplado ao SDK. A interface alvo deve usar apenas tipos do seu domínio — a conversão completa é o trabalho do adaptador.

  2. Regra de negócio escondida no adaptador

    Um 'if valor > 10000, dividir em duas cobranças' dentro do adaptador é uma decisão de negócio morando em uma camada de tradução. Quando o fornecedor for trocado, a regra some junto. Adaptador traduz; quem decide é o domínio.

  3. Adaptador sobre adaptador sobre adaptador

    Cada camada de adaptação extra é uma indireção que o leitor precisa atravessar para descobrir o que realmente acontece. Se você controla os dois lados, alinhe as interfaces e apague o adaptador — o padrão existe para fronteiras que você não controla.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Integrar uma biblioteca de terceiros com contrato diferente do seu domínio.
  • Isolar o resto do sistema de detalhes de um SDK/API externa (camada anticorrupção).
  • Reaproveitar uma classe existente cuja interface você não controla.

Evite quando

2
  • Você controla ambos os lados e pode simplesmente alinhar as interfaces.
  • Muitas camadas de adaptação empilhadas viram indireção que ninguém entende.

Testar o que ficou

3 armadilhas de Adapter viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • Wrappers de driverA camada fina que traduz a API de um serviço externo para a interface que o seu domínio espera.
  • Array.from(arguments)Converte um objeto parecido com array para um array de verdade, sem mudar a origem.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Traduz a interface de la para a que voce espera.
const repo = { salvar: (p) => sdkExterno.persist(paraDto(p)) };

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Uma tradução a manter sempre que qualquer um dos dois lados muda
  • Erros do sistema externo precisam ser traduzidos também, senão o acoplamento volta pelo catch

Não vale se: você controla os dois lados. Aí é mais barato mudar um deles do que manter um tradutor para sempre.

Costuma ser confundido com

Onde isto aparece

Continue por aqui