CodeGym /Blog Java /Random-ES /¿Qué problemas resuelve el patrón de diseño del adaptador...
Autor
Artem Divertitto
Senior Android Developer at United Tech

¿Qué problemas resuelve el patrón de diseño del adaptador?

Publicado en el grupo Random-ES
El desarrollo de software se hace más difícil debido a componentes incompatibles que necesitan trabajar juntos. Por ejemplo, si necesita integrar una nueva biblioteca con una plataforma antigua escrita en versiones anteriores de Java, puede encontrar objetos incompatibles o, más bien, interfaces incompatibles. ¿Qué problemas resuelve el patrón de diseño del adaptador?  - 1¿Qué hacer en este caso? ¿Reescribir el código? No podemos hacer eso, porque analizar el sistema llevará mucho tiempo o se violará la lógica interna de la aplicación. Para resolver este problema, se creó el patrón adaptador. Ayuda a los objetos con interfaces incompatibles a trabajar juntos. ¡Veamos cómo usarlo!

Más sobre el problema

Primero, simularemos el comportamiento del sistema antiguo. Supongamos que genera excusas por llegar tarde al trabajo oa la escuela. Para ello, dispone de una Excuseinterfaz que dispone de generateExcuse()y likeExcuse()métodos dislikeExcuse().

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
La WorkExcuseclase implementa esta interfaz:

public class WorkExcuse implements Excuse {
   private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
Probemos nuestro ejemplo:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Producción:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Ahora imagine que ha lanzado un servicio de generación de excusas, recopilado estadísticas y notado que la mayoría de sus usuarios son estudiantes universitarios. Para atender mejor a este grupo, le pidió a otro desarrollador que creara un sistema que genera excusas específicamente para estudiantes universitarios. El equipo de desarrollo realizó una investigación de mercado, clasificó las excusas, conectó algo de inteligencia artificial e integró el servicio con informes de tráfico, informes meteorológicos, etc. Ahora tienes una biblioteca para generar excusas para universitarios, pero tiene una interfaz diferente: StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Esta interfaz tiene dos métodos: generateExcuse, que genera una excusa, y dislikeExcuse, que evita que la excusa vuelva a aparecer en el futuro. La biblioteca de terceros no se puede editar, es decir, no puede cambiar su código fuente. Lo que tenemos ahora es un sistema con dos clases que implementan la Excuseinterfaz y una biblioteca con una SuperStudentExcuseclase que implementa la StudentExcuseinterfaz:

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
El código no se puede cambiar. La jerarquía de clases actual se ve así: ¿Qué problemas resuelve el patrón de diseño del adaptador?  - 2Esta versión del sistema solo funciona con la interfaz Excuse. No puede volver a escribir el código: en una aplicación grande, realizar dichos cambios podría convertirse en un proceso largo o romper la lógica de la aplicación. Podríamos introducir una interfaz base y expandir la jerarquía: ¿Qué problemas resuelve el patrón de diseño del adaptador?  - 3Para hacer esto, tenemos que cambiar el nombre de la Excuseinterfaz. Pero la jerarquía adicional no es deseable en aplicaciones serias: la introducción de un elemento raíz común rompe la arquitectura. Debe implementar una clase intermedia que nos permita usar la funcionalidad nueva y antigua con pérdidas mínimas. En resumen, necesitas un adaptador .

El principio detrás del patrón adaptador

Un adaptador es un objeto intermedio que permite que otro entienda las llamadas a métodos de un objeto. Implementemos un adaptador para nuestro ejemplo y llamémoslo Middleware. Nuestro adaptador debe implementar una interfaz que sea compatible con uno de los objetos. Déjalo Excuseser Esto permite Middlewarellamar a los métodos del primer objeto. Middlewarerecibe llamadas y las reenvía de forma compatible al segundo objeto. Aquí está la Middlewareimplementación con los métodos generateExcusey dislikeExcuse:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
Pruebas (en código de cliente):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
Producción:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
Una increíble excusa adaptada a las condiciones meteorológicas actuales, atascos o retrasos en los horarios del transporte público. El generateExcusemétodo simplemente pasa la llamada a otro objeto, sin cambios adicionales. El dislikeExcusemétodo requería que primero pusiéramos la excusa en la lista negra. La capacidad de realizar un procesamiento de datos intermedio es una de las razones por las que a la gente le encanta el patrón del adaptador. Pero, ¿qué pasa con el likeExcusemétodo, que es parte de la Excuseinterfaz pero no parte de la StudentExcuseinterfaz? La nueva funcionalidad no admite esta operación. El UnsupportedOperationExceptionfue inventado para esta situación. Se lanza si la operación solicitada no es compatible. Usémoslo. Así es como Middlewarese ve la nueva implementación de la clase:

public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
A primera vista, esta solución no parece muy buena, pero imitar la funcionalidad puede complicar la situación. Si el cliente presta atención y el adaptador está bien documentado, esta solución es aceptable.

Cuándo usar un adaptador

  1. Cuando necesita usar una clase de terceros, pero su interfaz es incompatible con la aplicación principal. El ejemplo anterior muestra cómo crear un objeto de adaptador que envuelve llamadas en un formato que un objeto de destino puede entender.

  2. Cuando varias subclases existentes necesitan alguna funcionalidad común. En lugar de crear subclases adicionales (lo que conducirá a la duplicación de código), es mejor usar un adaptador.

Ventajas y desventajas

Ventaja: el adaptador oculta al cliente los detalles del procesamiento de solicitudes de un objeto a otro. El código del cliente no piensa en formatear datos o manejar llamadas al método de destino. Es demasiado complicado y los programadores son perezosos :) Desventaja: el código base del proyecto se complica con clases adicionales. Si tiene muchas interfaces incompatibles, la cantidad de clases adicionales puede volverse inmanejable.

No confundas un adaptador con una fachada o decorador

Con solo una inspección superficial, un adaptador podría confundirse con los patrones de fachada y decorador. La diferencia entre un adaptador y una fachada es que una fachada introduce una nueva interfaz y envuelve todo el subsistema. Y un decorador, a diferencia de un adaptador, cambia el objeto mismo en lugar de la interfaz.

Algoritmo paso a paso

  1. Primero, asegúrese de tener un problema que este patrón pueda resolver.

  2. Defina la interfaz de cliente que se usará para interactuar indirectamente con objetos incompatibles.

  3. Haga que la clase de adaptador herede la interfaz definida en el paso anterior.

  4. En la clase de adaptador, cree un campo para almacenar una referencia al objeto adaptado. Esta referencia se pasa al constructor.

  5. Implemente todos los métodos de interfaz de cliente en el adaptador. Un método puede:

    • Pasar llamadas sin hacer ningún cambio

    • Modificar o complementar datos, aumentar/disminuir el número de llamadas al método de destino, etc.

    • En casos extremos, si un método en particular sigue siendo incompatible, lanza una UnsupportedOperationException. Las operaciones no admitidas deben documentarse estrictamente.

  6. Si la aplicación solo usa la clase de adaptador a través de la interfaz del cliente (como en el ejemplo anterior), entonces el adaptador se puede expandir sin problemas en el futuro.

Por supuesto, este patrón de diseño no es una panacea para todos los males, pero puede ayudarte a resolver con elegancia el problema de la incompatibilidad entre objetos con diferentes interfaces. Un desarrollador que conoce los patrones básicos está varios pasos por delante de aquellos que solo saben cómo escribir algoritmos, porque los patrones de diseño son necesarios para crear aplicaciones serias. La reutilización del código no es tan difícil y el mantenimiento se vuelve agradable. ¡Eso es todo por hoy! Pero pronto continuaremos conociendo varios patrones de diseño :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION