¿Tienes una biblioteca o módulo legado que funciona, pero no encaja con tu aplicación? El patrón Adapter traduce una interfaz a otra para que puedas reutilizar código sin modificarlo. Esta guía explica el patrón, ofrece ejemplos en TypeScript y React, y muestra cómo modernizar un módulo de Node.js basado en callbacks para usar async/await.
December 19, 2025 (7mo ago) — last updated May 3, 2026 (2mo ago)
Patrón Adapter en TypeScript, React y Node.js
Aprende a usar el patrón Adapter para unir interfaces incompatibles con ejemplos prácticos en TypeScript, React y Node.js y modernizar código legado.
← Back to blog
Patrón Adapter en TypeScript, React y Node.js
Resumen: Aprende a usar el patrón Adapter para unir interfaces incompatibles con ejemplos prácticos en TypeScript, React y Node.js y modernizar código legado.
Introducción
¿Tienes una biblioteca o módulo legado que funciona, pero no encaja con tu aplicación? El patrón Adapter traduce una interfaz a otra para que puedas reutilizar código sin modificarlo. Esta guía explica el patrón, ofrece ejemplos en TypeScript y React, y muestra cómo modernizar un módulo de Node.js basado en callbacks para usar async/await. También incluye buenas prácticas para pruebas y mantenimiento.
Por qué importa el patrón Adapter
El patrón Adapter es un patrón estructural que envuelve un objeto incompatible y expone la interfaz que tu código espera. Fue documentado por primera vez por la Gang of Four en 19941. Los adapters son clave para integrar APIs de terceros, modernizar código legado y unificar fuentes de datos.
Escenarios típicos donde los adapters ayudan:
- Integrar APIs de terceros con formatos de datos distintos.
- Modernizar APIs legadas basadas en callbacks para que funcionen con async/await.
- Unificar múltiples fuentes de datos en una sola interfaz para componentes de UI.
Usar un adapter mantiene la lógica de negocio limpia y desacoplada de sistemas externos.
Patrón Adapter en resumen
| Concepto | Descripción |
|---|---|
| Tipo | Estructural |
| Intención principal | Permitir que objetos con interfaces incompatibles trabajen juntos |
| Idea central | Envolver al adaptee para exponer la interfaz target |
| Problema clave resuelto | Reutilizar clases existentes sin cambiar su código fuente |
| Casos de uso comunes | Librerías de terceros, código legado, múltiples fuentes de datos |
Estructura y roles
El patrón tiene cuatro roles:
- El Cliente — el código que necesita una interfaz específica.
- La Interfaz Target — el contrato que el Cliente espera.
- El Adaptee — la clase o módulo incompatible con la funcionalidad necesaria.
- El Adapter — implementa el Target y delega al Adaptee, traduciendo llamadas según sea necesario.
Dos estilos comunes de adapter:
- Adapter por objeto (composición): el Adapter contiene una instancia del Adaptee. Es la aproximación más flexible.
- Adapter por clase (herencia): el Adapter hereda del Adaptee e implementa el Target. Requiere herencia múltiple y es menos común en JavaScript y TypeScript modernos.
Ejemplo práctico: TypeScript + React
Imagina un panel que recibe perfiles de usuario de dos servicios con formas de respuesta distintas. Sin un adapter, los componentes se llenan de lógica condicional.
Formas de API incompatibles
// Data from UserServiceA
interface UserA {
userId: number;
fullName: string;
emailAddress: string;
}
// Data from UserServiceB
interface UserB {
id: string;
name: string;
contact: {
email: string;
};
}
Interfaz target que nuestra app espera
interface UnifiedUser {
id: string;
name: string;
email: string;
}
Adapters en 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 las transformaciones mantiene los componentes limpios y resilientes. Si una API renombra un campo, solo cambia el adapter.
Componente React que consume datos 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 una única forma predecible, lo que facilita las pruebas y la reutilización. Para más contexto sobre herencia y polimorfismo, revisa la entrada relacionada en el blog sobre polimorfismo vs herencia: [/blog/polymorphism-vs-inheritance].
Ejemplo: Modernizando un módulo de Node.js basado en callbacks
Los módulos legados suelen usar callbacks con el patrón error-first. En lugar de modificar un módulo estable, construye un adapter que exponga una API basada en Promises.
Adaptee legado (no 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 devuelve una 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;
Este enfoque es similar a util.promisify en Node.js, pero mantiene la lógica de adaptación explícita y comprobable mediante pruebas3.
Uso del adapter en el código de la aplicación
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();
Esto mantiene el código legado intacto mientras ofrece al resto de tu base de código una interfaz moderna. También es útil durante migraciones incrementales, para reducir riesgo al desplegar cambios.
Cuándo usar un Adapter
Usa un adapter cuando dos componentes no pueden comunicarse directamente porque sus interfaces difieren. Escenarios típicos:
- Integrar una API de terceros cuyos inputs o outputs no coinciden con tus modelos.
- Envolver APIs legadas basadas en callbacks para que funcionen con async/await.
- Soportar múltiples fuentes de datos con diferentes formatos creando un adapter por fuente.
Cuándo no usar un adapter:
- Si controlas ambos sistemas y un pequeño refactor resolverá la discrepancia, prefiere el refactor directo.
- Si tu objetivo es simplificar un subsistema complejo, considera un Facade en su lugar. Un Facade ofrece un punto de entrada simplificado y de alto nivel; un Adapter se centra únicamente en la compatibilidad.
Lista rápida de decisión
| Situación | ¿Usar Adapter? | Por qué |
|---|---|---|
| Necesitas usar una librería de terceros con API incompatible | Sí | No puedes cambiar la librería, así que adáptate a ella |
| Controlas ambos lados y el cambio es pequeño | No | Refactoriza directamente para evitar indirección extra |
| Necesitas una interfaz de alto nivel simplificada para un sistema complejo | No | Facade es una mejor opción |
| Migrar sistemas legados de forma incremental | Sí | Envuelve componentes antiguos para que coincidan con nuevas interfaces |
| Múltiples fuentes de datos con estructuras diferentes | Sí | Los adapters las unifican en una sola forma |
Pruebas y rendimiento
Los adapters mejoran la capacidad de prueba al desacoplar la lógica central de sistemas externos. Puedes simular la interfaz de un adapter para probar componentes en aislamiento, y probar los adapters por separado para verificar la lógica de traducción.
La sobrecarga de rendimiento de un adapter es mínima, típicamente una llamada de función extra, y es insignificante frente a E/S de red o consultas a bases de datos. Para la mayoría de aplicaciones web, los beneficios de mantenimiento y desacoplamiento superan con creces el coste ínfimo. JavaScript sigue siendo el lenguaje más usado según la encuesta de desarrolladores de Stack Overflow en 20234.
Preguntas frecuentes
¿Qué problema resuelve el patrón Adapter?
Resuelve incompatibilidades de interfaz traduciendo llamadas de un cliente a llamadas que el adaptee entiende, para que puedas reutilizar código sin cambiarlo.
¿Cómo ayuda un Adapter con código legado?
Un Adapter envuelve módulos legados y expone una interfaz moderna, permitiéndote integrar código antiguo y estable en aplicaciones nuevas sin reescrituras arriesgadas.
¿Cuándo debo elegir un Adapter sobre otros patrones?
Elige un Adapter cuando necesites compatibilidad entre dos interfaces que no coinciden. Si quieres simplificar todo un subsistema, usa un Facade.
Lecturas adicionales y enlaces
- Adapter pattern overview and examples: [https://www.geeksforgeeks.org/adapter-pattern/]2
- Strategies for modernizing legacy systems: [/blog/modernizing-legacy-systems]
- Deep dive on polymorphism vs inheritance: [/blog/polymorphism-vs-inheritance]
- Node.js util.promisify documentation: [https://nodejs.org/api/util.html#utilpromisifyoriginal]3
En Clean Code Guy ayudamos a equipos a implementar patrones de diseño prácticos que convierten bases de código frágiles y complejas en activos resilientes, comprobables y agradables de trabajar. Si estás lidiando con un sistema legado o integraciones complicadas, nuestras Clean Code Audits pueden darte una hoja de ruta clara y accionable hacia una arquitectura más sana. Aprende cómo podemos ayudarte a lanzar mejor código, más rápido.
Preguntas rápidas (resumen)
Q: ¿Qué hace un Adapter? A: Traduce una interfaz a otra para permitir que componentes incompatibles trabajen juntos.
Q: ¿Qué gano al usar adapters en mi código front-end? A: Componentes más simples, pruebas más fáciles y cambios localizados cuando una API cambia.
Q: ¿Vale la pena adaptar código legado en lugar de reescribirlo? A: Sí, cuando quieres reducir riesgo y migrar de forma incremental mientras mantienes estabilidad.
La IA escribe código.Tú lo haces durar.
En la era de la aceleración de la IA, el código limpio no es solo una buena práctica — es la diferencia entre sistemas que escalan y bases de código que colapsan bajo su propio peso.