Abby's Digital Cafe

Patrón Estructural - Adapter

por Abigail Palmero (Abbytec)

El Arte de Traducir Código: ¿Qué es el Adapter?

Cuando empiezas a programar profesionalmente, te das cuenta de una verdad incómoda: rara vez escribirás código desde cero. La mayor parte de tu trabajo consistirá en conectar tu código nuevo con sistemas viejos, bases de datos heredadas o librerías de terceros que no controlas.

Aquí es donde surge el problema: Tu código espera recibir una "manzana", pero el sistema antiguo te da una "naranja". ¿La solución? No intentas convertir la naranja en manzana mágicamente; construyes un exprimidor que te dé el jugo. En términos de arquitectura, ese intermediario es el Patrón Adapter.

Es un patrón estructural que actúa como un puente entre dos interfaces incompatibles. Es el "adaptador de corriente" del mundo del software.

Componentes Clave del Adapter

Para entender cómo implementarlo, primero debemos identificar a los actores en esta obra:

  • Target (Objetivo): Es la interfaz que tu aplicación espera usar. Es el estándar que has definido en tu dominio.
  • Client (Cliente): Es la parte de tu código que colabora con los objetos que cumplen con la interfaz Target.
  • Adaptee (Adaptable): Es la clase existente, servicio de terceros o sistema legado que tiene una interfaz incompatible. Es útil, pero no podemos usarlo directamente.
  • Adapter (Adaptador): Es la clase que implementa la interfaz Target y envuelve al Adaptee. Recibe las llamadas del Cliente y las transforma en llamadas comprensibles para el Adaptee.

Ejemplo 1 (TypeScript): El problema del "Perfil de Usuario"

Situación típica de Frontend: Estás construyendo una tarjeta de perfil bonita que espera un objeto limpio. Pero el Backend (hecho hace años) te devuelve un objeto con nombres de propiedades extraños y formatos mezclados.

Tu misión es adaptar esa respuesta "sucia" a tu interfaz limpia sin ensuciar tus componentes de React/Angular/Vue.

[typescript]
// 1. TARGET (Tu código ideal) // Así es como tu componente quiere recibir los datos para pintarlos. interface IUserProfile { fullName: string; isActive: boolean; } // 2. ADAPTEE (La API antigua/externa) // Devuelve datos con nombres raros y formatos que no te sirven directamente. // Imagina que esto viene de un fetch() a un backend legacy. class OldBackendUser { getLegacyData() { return { // Nombre y apellido separados f_name: "Laura", l_name: "Gomez", // El estado viene como texto "YES"/"NO" en lugar de boolean status_code: "YES" }; } } // 3. ADAPTER (El traductor) class UserProfileAdapter implements IUserProfile { public fullName: string; public isActive: boolean; constructor(legacyUser: any) { // Lógica de transformación: Unimos nombre y apellido this.fullName = `${legacyUser.f_name} ${legacyUser.l_name}`; // Lógica de transformación: Convertimos "YES" a true this.isActive = legacyUser.status_code === "YES"; } } // --- USO EN TU APLICACIÓN --- // Simulación de respuesta del backend viejo const rawResponse = new OldBackendUser().getLegacyData(); // En lugar de usar 'rawResponse' directamente y llenar tu UI de lógica rara, // lo pasas por el adaptador. const user: IUserProfile = new UserProfileAdapter(rawResponse); console.log(user.fullName); // "Laura Gomez" (Limpio) console.log(user.isActive); // true (Booleano listo para usar)

Ejemplo 2 (Java): Migración de IDs de Empleados

Situación de Backend Corporativo: Tu empresa está modernizando su sistema de Recursos Humanos. El nuevo sistema (NewSystem) es eficiente y busca empleados por su ID numérico (Long). Sin embargo, los datos provienen de un archivo antiguo CSV o un sistema mainframe (LegacySystem) que guarda los IDs como texto (String), por ejemplo: "1050".

[java]
// 1. TARGET (La interfaz nueva) // Tu sistema moderno espera un número Long. interface IEmployeeService { String getEmployeeName(Long employeeId); } // 2. ADAPTEE (El sistema viejo) // Solo sabe buscar si le pasas un String. class LegacyHROperations { public String searchInDatabase(String idAsString) { // Simulación de búsqueda en base de datos vieja if (idAsString.equals("1050")) return "Carlos Ruiz"; return "Desconocido"; } } // 3. ADAPTER public class EmployeeAdapter implements IEmployeeService { private LegacyHROperations legacySystem; public EmployeeAdapter(LegacyHROperations legacySystem) { this.legacySystem = legacySystem; } @Override public String getEmployeeName(Long employeeId) { // PASO CRÍTICO DE ADAPTACIÓN: // El nuevo sistema envía un Long (1050), pero el viejo necesita String ("1050"). // Convertimos el tipo de dato aquí para que nadie más tenga que preocuparse. String idCompatible = String.valueOf(employeeId); // Delegamos el trabajo al sistema viejo return legacySystem.searchInDatabase(idCompatible); } } // 4. CLIENTE (Tu código Main) public class Main { public static void main(String[] args) { // Instanciamos el sistema viejo (el problema) LegacyHROperations oldSystem = new LegacyHROperations(); // Creamos el adaptador (la solución) IEmployeeService service = new EmployeeAdapter(oldSystem); // USABILIDAD: // Como programador moderno, yo paso un número (Long) y me olvido. // No necesito saber que por dentro se convierte a texto. Long searchId = 1050L; System.out.println(service.getEmployeeName(searchId)); } }

Preguntas de Entrevista sobre Adapter

¿El patrón Adapter cambia la funcionalidad del código?

No. Esta es una distinción clave. El Adapter solo se encarga de la traducción de la interfaz (cómo llamamos a los métodos y qué parámetros pasamos). No debería añadir lógica de negocio nueva ni alterar el resultado esperado, solo hacerlo compatible. Si empiezas a añadir lógica extra, te estás moviendo hacia el patrón Decorator o Proxy.

¿Por qué usar una Interfaz en el Target?

En los ejemplos (como IEmployeeService), usamos una interfaz para desacoplar el código. Esto permite que, si en el futuro migramos los datos y ya no necesitamos el sistema viejo, podamos crear una nueva clase ModernHROperations que implemente la misma interfaz, y cambiar el adaptador por la implementación real sin romper el resto de la aplicación (Polimorfismo).

¿Cuál es la diferencia con el patrón Facade?

Muchos principiantes los confunden. El Adapter hace que una cosa incompatible funcione con otra. El Facade (Fachada) toma muchas clases complejas y subsistemas y ofrece una interfaz simplificada. Piensa en el Adapter como un convertidor de enchufe y en el Facade como el panel de control simple de una central nuclear.

Conclusión

El Patrón Adapter es una herramienta fundamental de supervivencia para cualquier desarrollador. Te permite integrar sistemas caóticos de manera elegante, manteniendo tu base de código limpia y fuertemente tipada. La próxima vez que veas un error de tipo incompatible, no hagas un "hack" rápido; crea un adaptador.

Comentarios