DEV Community

Miguel Ángel Sánchez Chordi
Miguel Ángel Sánchez Chordi

Posted on • Originally published at Medium on

Una interfaz para controlarlos a todos: Patrón adapter

Este bicho lleva implementando el patrón millones de años

Siguiendo con la serie de patrones de diseño hoy vamos a ver un patrón estructural: el patrón adapter.

Introducción

Como buen patrón estructural, el cometido del patrón adapter está relacionado con la forma en la que los objetos interactúan entre ellos.

Para explicar un poco su cometido lo mejor es pensar en objetos de nuestro día a día, por ejemplo un adaptador de enchufes. Todos sabemos que las clavijas del enchufe europeo y del enchufe americano son distintas, y no solo eso, sino que existen muchos más tipos de enchufes:

Distintos tipos de clavijas

Cuando viajamos a Estados Unidos y empezamos a quedarnos sin batería en el móvil y decidimos ponerlo a cargar, nos podemos llevar una sorpresa si no hemos sido precavidos y hemos traído un adaptador europeo-americano:

Adaptador europeo/americano

Gracias a este pequeño cacharrito podremos cargar nuestro teléfono sin problemas. Y es que este pequeño dispositivo es la imagen perfecta de lo que hace el patrón:

  1. Las clavijas planas del adaptador representan la interfaz conocida.
  2. La parte negra representaría la clase adaptadora que hará que el elemento discordante (el enchufe del cargador) se comporte como es esperado.

Todo esto es posible, por supuesto, porque ambos enchufes (tanto el americano como el europeo) tienen una función igual o similar, que es conducir electricidad.

Ejemplo de implementación

La implementación de este patrón es bastante sencilla, solo hay que seguir unos sencillos pasos:

  1. Identificar a los actores: Esto sería determinar quién es el cliente y quiénes la parte a “adaptar”.
  2. Diseñar una interfaz común, una que conozca el cliente y que todos los futuros adaptados puedan implementar.
  3. Crear un adaptador que implemente dicha interfaz. Esto se haría para cada una de las partes a adaptar. Cada uno de estos adaptadores contendrá una instancia de la clase adaptada y “forzará” su uso para que se adapte a la nueva interfaz.
  4. Desacoplamos el cliente de la clase concreta. En lugar de recibir una clase concreta, a partir de ahora recibirá un objeto cualquiera que implemente la nueva interfaz.

A continuación vamos a ver un ejemplo de como sería la implementación de este patrón en un caso más o menos realista.

Supongamos que en nuestro proyecto necesitamos loguear constantemente todo lo que hacemos, y para ello no nos vale solo con un tipo de logger, sino que tenemos varios, concretamente tres tipos:

  • FileLogger: Escribe el log en ficheros locales. El método usado para registrar un log es addLine, recibe como parámetro un string.
  • DatabaseLogger: Escribe el log como registros de una tabla en una base de datos. El método usado para registrar un log es insertRecord, recibe como parámetro un string.
  • NetworkLogger: Envía los logs a un servicio de terceros que luego nos muestra informes con paneles y gráficas muy chulas. El método usado para registrar un log es sendLog, recibe como parámetro un objeto de tipo LogLine.

Por la otra parte, tenemos un servicio que hace uso de uno de estos servicios de log:

Como se puede apreciar, aunque estos 3 servicios realizan prácticamente la misma tarea, poseen 3 APIs muy distintas. Si quisiéramos reemplazar el logger que usa nuestro servicio por otro no solo necesitaríamos inyectar el servicio adecuado, sino que necesitaríamos reescribir el uso del mismo, ya que el la forma de usar el logger cambiaría.

Esto produce un fuerte acoplamiento entre el servicio y el logger… ¿Cómo podríamos evitarlo? Haciendo uso del patrón Adapter, obviamente ;)

Cómo se puede apreciar, ahora todos los adaptadores siguen la interfaz acordada (LoggerInterface) y el servicio puede confiar en que cualquier logger que reciba, implementará por contrato el método log, y eso es suficiente para nuestro servicio, ya que los detalles de implementación de cada logger concreto le resulta indiferente.

Ports & Adapters

Quizá hayas oído del término ports & adapters haciendo referencia a la arquitectura hexagonal, y es que en gran medida en esto se basa la arquitectura hexagonal.

Recordemos que en arquitectura hexagonal tenemos 3 capas principales:

  1. Application
  2. Domain
  3. Infrastructure

Habitualmente la capa Domain es la que contiene las interfaces que harán de puente entre nuestra lógica (Application) y los servicios de terceros (Infrastructure), de lo que deducimos que los “puertos” son las interfaces definidas en el dominio y los “adaptadores” son las implementaciones de esas interfaces que hacemos en la capa de infraestructura para acomodar servicios de terceros.

Conclusión

Como se puede ver este patrón está muy presente en nuestro día a día, y además es una herramienta muy potente que nos va a permitir:

  • Desacoplar nuestro código de las implementaciones de los servicios de terceros, forzando su adaptación a nuestro dominio.
  • Potenciar la cambiabilidad (podemos sustituir el servicio cambiando solo la inyección).
  • Simplificar nuestros tests (no necesitamos más que mockear la interfaz, sin necesidad de conocer detalles de implementación del código de terceros)

Nos vemos en el próximo post!

Referencias:


Top comments (0)