Aprende cómo el patrón Adapter conecta interfaces incompatibles con ejemplos en TypeScript, React y Node.js para modernizar integraciones y código legado.
December 19, 2025 (4mo ago) — last updated February 10, 2026 (2mo ago)
Patrón Adapter: Ejemplos en TypeScript, React y Node.js
Aprende cómo el patrón Adapter conecta interfaces incompatibles con ejemplos en TypeScript, React y Node.js para modernizar integraciones y código legado.
← Back to blog
Patrón Adapter: Ejemplos en TypeScript, React & Node.js
Resumen: Aprende cómo el patrón Adapter conecta interfaces incompatibles en TypeScript, React y Node.js con ejemplos prácticos y del mundo real.
Introducción
¿Alguna vez has tenido una biblioteca o un módulo legado perfectamente funcional que simplemente no encaja con el resto de tu sistema? Es como intentar enchufar un adaptador europeo en una toma norteamericana: ambos funcionan, pero sus interfaces no coinciden. El patrón Adapter lo soluciona traduciendo una interfaz a otra para que puedas reutilizar código existente sin cambiarlo.
Esta guía explica el patrón, muestra ejemplos en TypeScript y React, y demuestra un adaptador en Node.js que moderniza módulos basados en callbacks. Se centra en técnicas prácticas que puedes aplicar de inmediato para limpiar integraciones y mejorar la capacidad de prueba.
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 esenciales para integrar APIs de terceros, modernizar código legado y unificar fuentes de datos dispares.
Escenarios comunes donde los adapters ayudan:
- Integrar APIs de terceros que devuelven formas de datos diferentes.
- 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. Esta es la aproximación más flexible.
- Adapter por clase (herencia): el Adapter hereda del Adaptee e implementa el Target. Esto 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.
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;
Esto refleja el comportamiento de util.promisify de Node, pero mantiene la lógica de adaptación explícita y verificable 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.
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 comparada con 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 en la encuesta de desarrolladores de Stack Overflow, lo que subraya con qué frecuencia los desarrolladores enfrentan trabajo de integración que los adapters resuelven4.
Preguntas frecuentes
P: ¿Qué problema resuelve el patrón Adapter?
R: Resuelve incompatibilidades de interfaz traduciendo llamadas de un cliente a llamadas que el adaptee entiende para que puedas reutilizar código sin cambiarlo.
P: ¿Cómo ayuda un Adapter con código legado?
R: 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.
P: ¿Cuándo debo elegir un Adapter sobre otros patrones?
R: Elige un Adapter cuando necesites compatibilidad entre dos interfaces que no coinciden. Si quieres simplificar todo un subsistema, usa un Facade en su lugar.
Lecturas adicionales y enlaces internos
- Deep dive on polymorphism vs inheritance: /blog/polymorphism-vs-inheritance
- Strategies for modernizing legacy systems: /blog/modernizing-legacy-systems
- Adapter pattern reference and examples: GeeksforGeeks2
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.
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.