JavaRush /Blog Java /Random-ES /¿Qué problemas resuelve el patrón de diseño del Adaptador...

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

Publicado en el grupo Random-ES
El desarrollo de software suele complicarse por la incompatibilidad entre componentes que funcionan entre sí. Por ejemplo, si necesita integrar una nueva biblioteca con una plataforma antigua escrita en versiones anteriores de Java, puede encontrar incompatibilidad de objetos o, más precisamente, de interfaces. ¿Qué hacer en este caso? ¿Reescribir el código? Pero esto es imposible: analizar el sistema llevará mucho tiempo o se romperá la lógica interna del trabajo. ¿Qué problemas resuelve el patrón de diseño del Adaptador? - 1Para resolver este problema, idearon el patrón Adaptador, que ayuda a que objetos con interfaces incompatibles trabajen juntos. ¡Veamos cómo usarlo!

Más detalles sobre el problema.

Primero, simulemos el comportamiento del sistema antiguo. Digamos que genera motivos para llegar tarde al trabajo o a la escuela. Para ello disponemos de una interfaz Excuseque contiene los métodos generateExcuse(), likeExcuse()y dislikeExcuse().
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Esta interfaz es implementada por la clase WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все Cómo в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};

   @Override
   public String generateExcuse() { // Случайно выбираем отговорку из массива
       String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
               sorryOptions[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Дублируем элемент в массиве, чтобы шанс его выпадения был выше
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Удаляем элемент из массива
   }
}
Probemos el ejemplo:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Conclusión:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
Ahora imaginemos que lanzó el servicio, recopiló estadísticas y observó que la mayoría de los usuarios del servicio son estudiantes universitarios. Para mejorarlo y adaptarlo a las necesidades de este grupo, solicitó un sistema de generación de excusas a otro desarrollador específicamente para él. El equipo de desarrollo realizó investigaciones, recopiló calificaciones, conectó inteligencia artificial, agregó integración con atascos, clima, etc. Ahora tienes una biblioteca para generar excusas para los estudiantes, pero la interfaz para interactuar con ella es diferente StudentExcuse:
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
La interfaz tiene dos métodos: generateExcuse, que genera una excusa, y dislikeExcuse, que bloquea la excusa para que no aparezca en el futuro. Una biblioteca de terceros está cerrada a la edición; no se puede cambiar su código fuente. Como resultado, en su sistema hay dos clases que implementan la interfaz Excusey una biblioteca con una clase SuperStudentExcuseque implementa la interfaz StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки o сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
El código no se puede cambiar. El esquema actual se verá así: ¿Qué problemas resuelve el patrón de diseño Adaptador - 2?Esta versión del sistema sólo funciona con la interfaz Excuse. No se puede reescribir el código: en una aplicación grande, dichos cambios pueden llevar mucho tiempo o alterar la lógica de la aplicación. Puede sugerir introducir la interfaz principal y aumentar la jerarquía: ¿Qué problemas resuelve el patrón de diseño del Adaptador? - 3para hacer esto, debe cambiar el nombre de la interfaz Excuse. 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 le permita utilizar funcionalidades nuevas y antiguas con una pérdida mínima. En resumen, necesitas un adaptador .

Cómo funciona el patrón Adaptador

Un adaptador es un objeto intermedio que hace que las llamadas a métodos de un objeto sean comprensibles para otro. 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 ser Excuse. Gracias a esto, Middlewarepuede llamar a métodos del primer objeto. Middlewarerecibe llamadas y las pasa al segundo objeto en un formato compatible. Así es como se ve la implementación de un método Middlewarecon métodos generateExcusey dislikeExcuse:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с un objetoом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый un objeto
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse un objetoа superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
Pruebas (en código de cliente):
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются un objetoы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в un objeto-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
Conclusión:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
Una excusa increíble, adaptada a las condiciones meteorológicas actuales, atascos o alteraciones en el horario del transporte público. El método generateExcusesimplemente transfiere la llamada a otro objeto, sin transformaciones adicionales. El método dislikeExcuserequería primero colocar la excusa en una lista negra de una base de datos. El procesamiento de datos intermedio adicional es la razón por la que se ama el patrón Adaptador. Pero ¿qué pasa con un método likeExcuseque está en la interfaz Excuse, pero no en StudentExcuse? Esta operación no es compatible con la nueva funcionalidad. Para este caso, se les ocurrió una excepción UnsupportedOperationException: se lanza si la operación solicitada no es compatible. Usemos esto. Así es como se ve la implementación de la nueva clase Middleware:
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("Метод likeExcuse не поддерживается в новом функционале");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Метод обращается за дополнительной информацией к БД,
       // Затем передает ее в метод dislikeExcuse un objetoа superStudentExcuse.
   }
}
A primera vista, esta solución no parece exitosa, pero simular la funcionalidad puede llevar a una situación más compleja. Si el cliente está atento y el adaptador está bien documentado, esta solución es aceptable.

Cuándo utilizar el adaptador

  1. Si necesita utilizar una clase de terceros, pero su interfaz no es compatible con la aplicación principal. El ejemplo anterior muestra cómo se crea un objeto shim que envuelve las llamadas en un formato comprensible para el objeto de destino.

  2. Cuando varias subclases existentes deben tener una funcionalidad común. En lugar de subclases adicionales (su creación dará lugar a la duplicación de código), es mejor utilizar 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 los datos ni en manejar llamadas al método de destino. Es demasiado complicado y los programadores son vagos :) Desventaja: la base del código del proyecto se complica con clases adicionales, y si hay una gran cantidad de puntos incompatibles, su número puede crecer hasta tamaños incontrolables.

No confundir con Fachada y Decorador

Tras un examen superficial, el Adaptador se puede confundir con los patrones Fachada y Decorador. La diferencia entre un Adaptador y una Fachada es que una Fachada introduce una nueva interfaz y envuelve un subsistema completo. Bueno, el Decorador, a diferencia del Adaptador, cambia el objeto en sí, no la interfaz.

Algoritmo de implementación paso a paso

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

  2. Defina una interfaz de cliente en nombre de la cual se utilizará otra clase.

  3. Implemente una clase de adaptador basada en la interfaz definida en el paso anterior.

  4. En la clase de adaptador, cree un campo que almacene una referencia al objeto. Esta referencia se pasa en el constructor.

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

    • Transferir la llamada sin modificación;

    • Cambiar datos, aumentar/disminuir el número de llamadas al método de destino, ampliar aún más la composición de los datos, etc.

    • Como último recurso, si un método en particular es incompatible, lanza una excepción UnsupportedOperationException, que debe documentarse estrictamente.

  6. Si la aplicación usa el adaptador solo a través de la interfaz del cliente (como en el ejemplo anterior), esto permitirá que los adaptadores se extiendan sin problemas en el futuro.

Por supuesto, un patrón de diseño no es una panacea para todos los males, pero con su ayuda se puede resolver elegantemente el problema de la incompatibilidad de objetos con diferentes interfaces. Un desarrollador que conoce los patrones básicos está varios pasos por encima de aquellos que simplemente saben cómo escribir algoritmos, porque son necesarios para crear aplicaciones serias. Reutilizar código se vuelve menos difícil y mantenerlo es un placer. ¡Eso es todo por hoy! Pero pronto continuaremos conociendo diferentes patrones de diseño :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION