Já teve uma biblioteca ou módulo legado que não se encaixa no resto do seu sistema? O padrão Adapter traduz uma interface para outra, permitindo reutilizar código existente sem alterá‑lo. Este guia mostra exemplos práticos em TypeScript, React e Node.js para modernizar integrações e melhorar a testabilidade.
December 19, 2025 (10mo ago) — last updated October 4, 2026 (18d ago)
Padrão Adapter em TypeScript, React e Node.js
Entenda o padrão Adapter com exemplos práticos em TypeScript, React e Node.js para modernizar integrações e adaptar código legado.
← Back to blog
Padrão Adapter em TypeScript, React e Node.js
Resumo: Aprenda como o padrão Adapter conecta interfaces incompatíveis em TypeScript, React e Node.js com exemplos práticos para modernizar integrações e código legado.
Introdução
Já teve uma biblioteca ou módulo legado que não se encaixa no resto do seu sistema? É como tentar colocar um adaptador europeu numa tomada norte‑americana: ambos funcionam, mas as interfaces não combinam. O padrão Adapter traduz uma interface para outra, permitindo reutilizar código existente sem alterá‑lo.
Este guia explica o padrão, mostra exemplos em TypeScript e React e apresenta um adapter em Node.js que moderniza módulos baseados em callbacks. O foco são técnicas práticas que você pode aplicar imediatamente para limpar integrações e melhorar a testabilidade.
Por que o Padrão Adapter é importante
O Adapter é um padrão estrutural que envolve um objeto incompatível e expõe a interface que seu código espera. Foi documentado pelo Gang of Four em 19941. Adapters são essenciais para integrar APIs de terceiros, modernizar código legado e unificar fontes de dados díspares.
Cenários comuns onde adapters ajudam:
- Integrar APIs de terceiros que retornam formatos diferentes.
- Modernizar APIs legadas baseadas em callbacks para async/await.
- Unificar múltiplas fontes de dados em uma única interface para componentes de UI.
Usar um adapter mantém a lógica de negócio limpa e desacoplada de sistemas externos.
Padrão Adapter em resumo
| Conceito | Descrição |
|---|---|
| Tipo | Estrutural |
| Intenção principal | Permitir que objetos com interfaces incompatíveis trabalhem juntos |
| Ideia central | Envolver o adaptee para expor a interface alvo |
| Problema resolvido | Reusar classes existentes sem alterar o código-fonte |
| Casos comuns | Bibliotecas de terceiros, código legado, múltiplas fontes de dados |
Estrutura e papéis
O padrão tem quatro papéis:
- Cliente — o código que precisa de uma interface específica.
- Interface alvo — o contrato que o Cliente espera.
- Adaptee — a classe ou módulo incompatível com a funcionalidade necessária.
- Adapter — implementa a Interface alvo e delega para o Adaptee, traduzindo chamadas conforme necessário.
Dois estilos comuns de adapter:
- Adapter por objeto (composição): o Adapter mantém uma instância do Adaptee; é a abordagem mais flexível.
- Adapter por classe (herança): o Adapter herda do Adaptee e implementa a Interface alvo; é menos comum em JavaScript/TypeScript modernos.
Exemplo prático: TypeScript + React
Imagine um painel que recebe perfis de usuário de dois serviços com formatos distintos. Sem um adapter, os componentes ficam cheios de lógica condicional.
Formatos de API incompatíveis
// Data from UserServiceA
interface UserA {
userId: number;
fullName: string;
emailAddress: string;
}
// Data from UserServiceB
interface UserB {
id: string;
name: string;
contact: {
email: string;
};
}
Interface alvo que nossa aplicação espera
interface UnifiedUser {
id: string;
name: string;
email: string;
}
Adapters em TypeScript
// Adapter for UserServiceA
function adaptUserA(userA: UserA): UnifiedUser {
return {
id: userA.userId.toString(),
name: userA.fullName,
email: userA.emailAddress,
};
}
// Adapter for UserServiceB
function adaptUserB(userB: UserB): UnifiedUser {
return {
id: userB.id,
name: userB.name,
email: userB.contact.email,
};
}
Centralizar transformações mantém os componentes limpos e resilientes. Se uma API renomear um campo, apenas o adapter muda.
Componente React que consome dados unificados
interface UserProfileProps {
user: UnifiedUser;
}
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
return (
<div>
<h2>{user.name}</h2>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
</div>
);
};
Este componente depende de uma única forma previsível, o que facilita testes e reutilização.
Exemplo: modernizando um módulo Node.js baseado em callback
Módulos legados frequentemente usam callbacks com erro como primeiro parâmetro. Em vez de modificar um módulo estável, construa um adapter que exponha uma API baseada em Promise.
Adaptee legado (não modificar)
// legacyFileProcessor.js
const fs = require('fs');
class LegacyFileProcessor {
processFile(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
return callback(err, null);
}
const processedContent = data.toUpperCase();
callback(null, processedContent);
});
}
}
module.exports = LegacyFileProcessor;
Adapter que retorna uma Promise
// FileProcessorAdapter.js
const LegacyFileProcessor = require('./legacyFileProcessor');
class FileProcessorAdapter {
constructor() {
this.legacyProcessor = new LegacyFileProcessor();
}
processFile(filePath) {
return new Promise((resolve, reject) => {
this.legacyProcessor.processFile(filePath, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
}
module.exports = FileProcessorAdapter;
Isto espelha o comportamento do util.promisify do Node, mas mantém a lógica de adaptação explícita e testável3.
Usando o adapter no código da aplicação
const FileProcessorAdapter = require('./FileProcessorAdapter');
const fileProcessor = new FileProcessorAdapter();
async function handleFileProcessing() {
try {
console.log('Processing file with modern async/await...');
const content = await fileProcessor.processFile('my-file.txt');
console.log('Processed Content:', content);
} catch (error) {
console.error('An error occurred:', error);
}
}
handleFileProcessing();
Isso mantém o código legado intocado enquanto oferece ao restante da base de código uma interface moderna.
Quando usar um Adapter
Use um adapter quando dois componentes não conseguem se comunicar diretamente porque suas interfaces diferem. Cenários típicos:
- Integrar uma API de terceiros cujas entradas ou saídas não correspondem aos seus modelos.
- Envolver APIs legadas baseadas em callbacks para que funcionem com async/await.
- Suportar múltiplas fontes de dados com formatos diferentes criando um adapter por fonte.
Quando não usar um adapter:
- Se você controla ambos os sistemas e um refactor pequeno resolveria a incompatibilidade, prefira refatorar diretamente.
- Se o objetivo é simplificar um subsistema complexo, considere um Facade em vez disso. Um Facade oferece um ponto de entrada de alto nível; um Adapter foca apenas na compatibilidade.
Lista rápida de decisão
| Situação | Usar Adapter? | Por quê |
|---|---|---|
| Usar uma biblioteca de terceiros com API incompatível | Sim | Não é possível alterar a biblioteca |
| Controla ambos os lados e a mudança é pequena | Não | Refatore diretamente para evitar indireção |
| Precisa de uma interface de alto nível simplificada | Não | Facade é mais adequado |
| Migrar sistemas legados incrementalmente | Sim | Envolva componentes antigos para corresponder às novas interfaces |
| Múltiplas fontes de dados com formatos diferentes | Sim | Adapters unificam em uma forma única |
Testes e performance
Adapters melhoram a testabilidade ao desacoplar a lógica central de sistemas externos. Você pode mockar a interface do adapter para testar componentes isoladamente e testar adapters separadamente para verificar a lógica de tradução.
A sobrecarga de performance de um adapter é mínima — tipicamente uma chamada de função extra — e é insignificante comparada a operações de rede ou consultas a banco de dados. Para a maioria das aplicações web, os benefícios de manutenção e desacoplamento superam esse custo. JavaScript continua sendo a linguagem mais usada segundo pesquisas da comunidade, o que evidencia a frequência de trabalho de integração que adapters resolvem4.
Perguntas frequentes
Qual problema o padrão Adapter resolve?
Ele resolve incompatibilidades de interface traduzindo chamadas de um cliente em chamadas que o adaptee entende, permitindo reutilizar código sem alterá‑lo.
Como um Adapter ajuda com código legado?
Um Adapter envolve módulos legados e expõe uma interface moderna, permitindo integrar código antigo e estável em novas aplicações sem refatorações arriscadas.
Quando escolher um Adapter em vez de outros padrões?
Escolha um Adapter quando precisar compatibilizar duas interfaces incompatíveis. Se você quer simplificar todo um subsistema, use um Facade.
Leitura adicional
- Deep dive on polymorphism vs inheritance: [./blog/polymorphism-vs-inheritance]
- Strategies for modernizing legacy systems: [./blog/modernizing-legacy-systems]
- Adapter pattern reference and examples: [https://www.geeksforgeeks.org/adapter-pattern/]2
At Clean Code Guy, we help teams implement practical design patterns that turn brittle, complex codebases into assets that are resilient, testable, and a pleasure to work on. If you’re wrestling with a legacy system or tricky integrations, our Clean Code Audits can give you a clear, actionable roadmap to a healthier architecture. Learn how we can help you ship better code, faster.
Resumo em perguntas rápidas
Q: O Adapter altera o código legado? A: Não — ele envolve o código legado e expõe a interface esperada.
Q: Adapters afetam muito a performance? A: Não — o custo é mínimo e raramente relevante frente a I/O ou consultas.
Q: Adapter ou Facade? A: Use Adapter para compatibilidade de interfaces; use Facade para simplificar um subsistema.
IA escreve código.Você faz durar.
Na era da aceleração da IA, código limpo não é apenas uma boa prática — é a diferença entre sistemas que escalam e bases de código que entram em colapso sob seu próprio peso.