Adapter Pattern

Intención y problema

¿Cómo permitir colaboración entre interfaces incompatibles sin modificar necesariamente los participantes originales? Adapter presenta al Client el contrato Target que necesita y traduce esa conversación hacia un Adaptee existente. El extracto original GoF sobre Adapter desarrolla la intención y sus formulaciones por clase y objeto.

Es un Structural Pattern: organiza una frontera de compatibilidad. No exige que toda diferencia desaparezca ni que los sistemas tengan el mismo significado. Adaptar puede volver utilizable una capacidad; no puede fabricar garantías que el proveedor no tiene.

Imaginemos un cotizador de envíos. El cliente espera una solicitud con peso en kilogramos y un resultado monetario explícito. Una biblioteca legada recibe gramos y códigos de zona, y devuelve centavos con códigos propios de error. Cambiar todos los callers para hablar ese protocolo dispersaría conocimiento del legado. Cambiar el proveedor puede ser imposible o costoso.

Forces y frontera de compatibilidad

Queremos reutilizar una capacidad sin contaminar consumidores con detalles ajenos. Al mismo tiempo, la traducción debe ser fiel y observable: ocultar una diferencia importante puede causar más daño que exponer una incompatibilidad.

El contrato Target debe expresar lo que el cliente necesita, no copiar cada peculiaridad del Adaptee. Pero prometer cotización garantizada cuando el proveedor ofrece una estimación aproximada sería incorrecto. La conversación común tiene límites que deben acordarse.

También hay tensión entre aislamiento y costo. Una frontera adicional concentra conversiones y facilita reemplazar un proveedor, pero agrega código, diagnóstico y mantenimiento frente a cambios en ambos lados. Una llamada estable que ya satisface el contrato puede no necesitar ningún Adapter.

Estructura y colaboración

Client → Target: quote(shippingRequest)
             ↑ implements
         ShippingAdapter
             ↓ delegates after translation
         LegacyRateCalculator

Client usa Target. Target define la conversación esperada. Adaptee ofrece una capacidad útil mediante otra interfaz. Adapter satisface Target y delega al Adaptee, traduciendo entradas, secuencia de operaciones, resultados o errores según necesidad.

En nuestro ejemplo, la adaptación verifica unidades, convierte peso, obtiene un código de zona válido, llama al cotizador legado y produce un resultado con moneda y precisión declaradas. Los errores desconocidos necesitan una salida explícita; traducirlos silenciosamente a éxito rompe la colaboración.

Un protocol mismatch puede requerir varias llamadas donde Target ofrece una operación. Eso obliga a definir resultados parciales, ownership y orden. Convertir dos llamadas en un método no crea atomicidad. El Adapter puede coordinar la traducción sin esconder que una segunda operación falló después de la primera.

No hace falta ofrecer todas las capacidades originales. Tampoco conviene filtrar el objeto legado por la interfaz Target: si los clientes necesitan conocerlo para completar cada operación, la frontera probablemente no está conteniendo la incompatibilidad.

Object Adapter y Class Adapter

Formulación clásicaMecanismoConsecuencia relevante
Object AdapterMantiene un Adaptee y delega mediante composición.Puede envolver distintas instancias compatibles; agrega una referencia y su lifecycle.
Class AdapterUsa herencia para combinar Target y comportamiento del Adaptee.Depende de posibilidades y restricciones de herencia del lenguaje.

La variante clásica por clase puede requerir herencia múltiple o una combinación de herencia de implementación e interfaz. No todos los lenguajes la expresan convenientemente. Eso no invalida Adapter: composición y delegación suelen ser más portables y permiten adaptar sin heredar una implementación concreta.

Elegir objeto tampoco elimina costos. Hay que decidir quién construye y libera al Adaptee, si puede compartirse y cómo se accede a capacidades no representadas por Target. La forma por clase puede ser apropiada cuando la extensión mediante herencia está prevista y sus restricciones son aceptables.

Funciones de conversión, extension wrappers y composición de módulos pueden reducir ceremony en lenguajes modernos. Una función que presenta un contrato esperado y delega hacia otro incompatible puede cumplir la intención sin una jerarquía. Una conversión aislada de datos, sin esa colaboración, no necesita etiquetarse automáticamente como Adapter.

Interface compatibility y semantic compatibility

interoperability evalúa intercambio y uso correcto entre participantes. Que firmas y tipos encajen no demuestra equivalencia semántica. Deben revisarse unidades, precisión, garantías, errores, efectos y significado de ausencia.

En el escenario propio, Target permite peso fraccional en kilogramos y el legado acepta gramos enteros. Redondear puede cambiar la tarifa. El Adapter debe establecer una política acordada o rechazar casos que no puede representar fielmente. Nombrar ambas operaciones quote no resuelve esa diferencia.

La transformación inversa tampoco siempre existe. Si un sistema guarda precios con cuatro decimales y otro solo centavos, convertir y reconstruir pierde información. Una transformación bidireccional prometida como reversible sería falsa. Conservar datos originales, delimitar operaciones o rechazar la conversión son alternativas, según el contrato.

DiferenciaRiesgo de ocultarla
Estimación frente a precio confirmado.El cliente interpreta una garantía inexistente.
Ausente frente a cero.Un dato desconocido aparece como valor conocido.
Aceptado frente a ejecutado.El caller supone un efecto todavía pendiente.

El límite no es un defecto que pueda arreglarse agregando más mapping. Algunas incompatibilidades requieren renegociar Target o elegir otro proveedor.

GoF Adapter y Ports and Adapters

En ports-and-adapters, adapter designa un participante arquitectónico que conecta actores o tecnologías externas con un port de aplicación. Cockburn relaciona esa arquitectura con Adapter en su formulación original. Esa relación no convierte cada participante de frontera en la misma colaboración de diseño.

Application Port
      ↑ implements
SQL Persistence Adapter

El dibujo solo muestra un port implementado por un componente de persistencia. Este puede usar consultas directas, varias funciones o un cliente de database. Para reconocer GoF Adapter habría que identificar además la interfaz incompatible y su traducción hacia la esperada.

Un componente puede cumplir ambos papeles: implementar un port arquitectónico y envolver una biblioteca cuya conversación necesita adaptar. También puede existir un GoF Adapter local dentro del núcleo, sin representar una frontera externa. GoF Adapter y architectural adapter role no son identidades equivalentes; explicar cada intención evita deducir arquitectura por nombres.

Adapter, Facade, Decorator y Proxy

Adapter busca compatibilidad con una conversación esperada. Facade ofrece acceso simplificado a un subsistema; puede ser útil aunque no exista ninguna interfaz incompatible. La simplificación puede acompañar una adaptación, pero no es su objetivo definitorio.

decorator-pattern generalmente conserva un contrato compatible y agrega responsabilidades componibles. Adapter traduce hacia el contrato que espera el cliente. Esta comparación distingue intenciones, no impone una regla sintáctica absoluta: interfaces parecidas pueden tener protocolos incompatibles y una adaptación puede incluir comportamiento adicional necesario.

proxy-pattern conserva el contrato esperado mientras representa o controla acceso. Adapter traduce una conversación incompatible. Si el acceso remoto exige traducción interna, una implementación puede reunir ambas intenciones; firmas y delegación no bastan para identificarla.

Un wrapper no permite decidir entre los cuatro. Hay que reconstruir qué problema resuelve: traducción, simplificación, responsabilidad agregada o representación y control de acceso. pattern-selection ayuda a evaluar esa presión frente a una llamada directa.

Consecuencias, pruebas y misuses

Adapter contiene conocimiento de un proveedor y puede facilitar pruebas con implementaciones alternativas de Target. A cambio agrega indirection y un componente que debe evolucionar si cambian ambos contratos. No elimina incompatibilidades semánticas ni errores de integración.

Las pruebas relevantes incluyen límites de precisión, resultados parciales, datos ausentes, códigos desconocidos y preservación de efectos. Comprobar solamente que se delegó una llamada no verifica compatibilidad. Un fixture representativo del proveedor ayuda a contrastar el contrato, sin volver universal su comportamiento observado.

No ayuda cuando las interfaces ya son compatibles o cuando el cliente necesita casi todo el protocolo original. Es misuse acumular reglas de negocio ajenas a la traducción, esconder pérdidas bidireccionales, aceptar errores como resultados normales o llamar Adapter a cualquier wrapper.

design-pattern conserva la descripción de intención y consecuencias; structural-patterns permite contrastar otras soluciones estructurales. La adaptación tiene éxito cuando el cliente puede colaborar mediante un contrato honesto, no cuando dejan de verse los nombres del proveedor.