C
|Caroline Oliveira
Estudos

Design Patterns no Dia a Dia: Adapter

Data de publicação

Excalidraw contendo o esquemático de como um adapter funciona.

Design Patterns no Dia a Dia: Adapter

Tem uma situação que provavelmente você já viveu: você está no meio de uma feature, precisa integrar uma biblioteca externa ou um serviço legado, e as interfaces simplesmente não conversam. O contrato que o seu código espera é diferente do que aquele componente entrega.

Durante um tempo eu resolvia isso na força bruta — jogava conversões no meio do código de negócio, criava funções utilitárias espalhadas, ou pior, modificava o componente legado e torcia para nada quebrar. Funcionava. Mas sempre voltava para me assombrar.

Foi mergulhando em design patterns que entendi que esse problema tem nome, tem solução e tem estrutura. O padrão chama Adapter.


O que é o Adapter

O Adapter é um padrão estrutural que permite que objetos com interfaces incompatíveis trabalhem juntos. Ele age como um intermediário: traduz a interface que o cliente conhece para a interface que o serviço oferece.

A analogia mais direta: o adaptador de tomada. Sua fonte de notebook tem um plug americano, a parede tem uma tomada europeia. Você não refaz a instalação elétrica do prédio nem compra um notebook novo — você coloca um adaptador no meio.

Quando aplicar

  • Você precisa usar uma classe existente (legada ou de terceiros) mas a interface dela não bate com o que seu código espera.
  • Você quer reaproveitar subclasses existentes sem duplicar código, adicionando funcionalidade via composição.
  • A interface do serviço externo pode mudar — e você quer isolar esse impacto num único ponto.

O sinal de alerta mais claro: você está escrevendo conversões de dados ou chamadas de adaptação espalhadas por vários lugares. Esse código deveria estar centralizado num Adapter.

Estrutura

O padrão tem quatro participantes principais:

  1. Client — o código que usa a funcionalidade. Só conhece a interface Target.
  2. Target — a interface que o Client espera. Em Go, será uma interface com o método desejado.
  3. Adaptee — a classe/struct existente com a interface incompatível. Pode ser legada ou de terceiros.
  4. Adapter — implementa Target e internamente usa o Adaptee. É aqui que a tradução acontece.


Exemplo em Go

Imagine que seu sistema usa um Logger com a interface:

Go
type Logger interface {
Log(message string)
}

Mas você precisa integrar uma biblioteca de observabilidade legada que expõe:

Go
type LegacyLogger struct{}
func (l *LegacyLogger) WriteLog(level, msg string) {
fmt.Printf("[%s] %s\n", level, msg)
}

O Client espera Log(message string). O LegacyLogger oferece WriteLog(level, msg string). Interfaces incompatíveis.

O Adapter resolve isso:

Go
// Adapter: implementa Logger, usa LegacyLogger internamente
type LegacyLoggerAdapter struct {
legacy *LegacyLogger
}
func NewLegacyLoggerAdapter(l *LegacyLogger) *LegacyLoggerAdapter {
return &LegacyLoggerAdapter{legacy: l}
}
func (a *LegacyLoggerAdapter) Log(message string) {
a.legacy.WriteLog("INFO", message)
}

Uso no Client:

Go
func main() {
legacy := &LegacyLogger{}
logger := NewLegacyLoggerAdapter(legacy)
// O client só conhece a interface Logger
logger.Log("Sistema iniciado")
}

O Client não sabe nada do LegacyLogger. Se futuramente a biblioteca mudar, você ajusta apenas o Adapter.

⚠️ Ponto de atenção:

  • Aumenta o número de classes/structs. Se a interface do serviço for simples e estável, talvez uma conversão direta seja suficiente. Não force o padrão onde ele não agrega.



Adapter vs padrões próximos

É comum confundir o Adapter com outros padrões estruturais:

  • Facade também simplifica acesso a código complexo, mas ele define uma interface nova para um subsistema inteiro. O Adapter faz uma interface existente funcionar onde outra é esperada — ele não define, ele traduz.
  • Decorator mantém ou estende a interface original. O Adapter substitui uma interface por outra.
  • Proxy mantém a mesma interface e adiciona comportamento (cache, controle de acesso). O Adapter muda a interface.




Exercício intermediário

Contexto: Você está construindo um sistema de pagamentos. Seu código de checkout usa a seguinte interface:

Go
type PaymentGateway interface {
Charge(amount float64, currency string) error
}

Uma nova parceira de pagamentos foi contratada e a SDK dela expõe:

Sua tarefa:

  1. Implemente um ExternalPaymentAdapter que satisfaça a interface PaymentGateway.
  2. O adapter deve converter float64 (reais/dólares) para int (centavos) antes de repassar ao SDK.
  3. Se o SDK retornar false, o adapter deve retornar um error com a mensagem recebida.
  4. Escreva um main que usa o adapter via interface PaymentGateway para cobrar R$ 49,90 em BRL.

.

.

.

Go
package main
import (
"errors"
"fmt"
"math"
)
// Interface esperada pelo sistema
type PaymentGateway interface {
Charge(amount float64, currency string) error
}
// SDK externo — não podemos alterar
type ExternalPaymentSDK struct{}
func (e *ExternalPaymentSDK) ProcessPayment(cents int, currencyCode string) (bool, string) {
if cents <= 0 {
return false, "invalid amount"
}
return true, "txn_" + currencyCode
}
// Adapter
type ExternalPaymentAdapter struct {
sdk *ExternalPaymentSDK
}
func NewExternalPaymentAdapter(sdk *ExternalPaymentSDK) *ExternalPaymentAdapter {
return &ExternalPaymentAdapter{sdk: sdk}
}
func (a *ExternalPaymentAdapter) Charge(amount float64, currency string) error {
// Converte float64 para centavos (int)
cents := int(math.Round(amount * 100))
success, msg := a.sdk.ProcessPayment(cents, currency)
if !success {
return errors.New(msg)
}
fmt.Printf("Pagamento aprovado: %s (txn: %s)\n", currency, msg)
return nil
}
// Client — só conhece PaymentGateway
func checkout(gateway PaymentGateway, amount float64, currency string) {
err := gateway.Charge(amount, currency)
if err != nil {
fmt.Printf("Falha no pagamento: %v\n", err)
return
}
fmt.Println("Checkout concluído com sucesso.")
}
func main() {
sdk := &ExternalPaymentSDK{}
gateway := NewExternalPaymentAdapter(sdk)
checkout(gateway, 49.90, "BRL")
}

O ponto central: checkout não sabe que existe um ExternalPaymentSDK. Se a parceira mudar a SDK, você atualiza só o ExternalPaymentAdapter. O resto do sistema não precisa ser alterado.



Conclusão

Design patterns não são receitas para seguir cegamente. São vocabulário para nomear problemas que você já encontrou e ferramentas para resolvê-los com clareza. O Adapter foi um dos primeiros que me fez pensar: "ah, então é isso que eu estava tentando fazer nas últimas semanas".