JavaRush /Blog Java /Random-ES /Patrón de diseño de proxy

Patrón de diseño de proxy

Publicado en el grupo Random-ES
En programación, es importante planificar adecuadamente la arquitectura de la aplicación. Una herramienta indispensable para ello son los patrones de diseño. Hoy hablaremos de Proxy, o lo que es lo mismo, Diputado.

¿Por qué necesitas un diputado?

Este patrón ayuda a resolver problemas asociados con el acceso controlado a un objeto. Quizás tenga una pregunta: "¿Por qué necesitamos un acceso tan controlado?" Veamos un par de situaciones que le ayudarán a descubrir qué es qué.

Ejemplo 1

Imaginemos que tenemos un proyecto grande con un montón de código antiguo, donde hay una clase responsable de descargar informes de la base de datos. La clase funciona de forma sincrónica, es decir, todo el sistema está inactivo mientras la base de datos procesa la solicitud. En promedio, se genera un informe en 30 minutos. Gracias a esta característica, su carga comienza a las 00:30 y la administración recibe este informe por la mañana. Durante el análisis, resultó que es necesario recibir el informe inmediatamente después de su generación, es decir, dentro de un día. Es imposible reprogramar la hora de inicio, ya que el sistema esperará una respuesta de la base de datos. La solución es cambiar el principio operativo iniciando la carga y la generación de informes en un hilo separado. Esta solución permitirá que el sistema funcione como de costumbre y la administración recibirá informes nuevos. Sin embargo, hay un problema: el código actual no se puede reescribir, ya que sus funciones son utilizadas por otras partes del sistema. En este caso, puede introducir una clase de proxy intermedia utilizando el patrón Adjunto, que recibirá una solicitud para cargar un informe, registrar la hora de inicio y lanzar un hilo separado. Cuando se genere el informe, el hilo completará su trabajo y todos estarán contentos.

Ejemplo 2

El equipo de desarrollo crea un sitio web de carteles. Para obtener datos sobre nuevos eventos, recurren a un servicio de terceros, cuya interacción se realiza a través de una biblioteca cerrada especial. Durante el desarrollo, surgió un problema: un sistema de terceros actualiza los datos una vez al día y se produce una solicitud cada vez que el usuario actualiza la página. Esto crea una gran cantidad de solicitudes y el servicio deja de responder. La solución es almacenar en caché la respuesta del servicio y proporcionar a los visitantes el resultado guardado en cada reinicio, actualizando este caché según sea necesario. En este caso, utilizar el patrón Adjunto es una excelente solución sin cambiar la funcionalidad final.

Cómo funciona el patrón

Para implementar este patrón, necesita crear una clase de proxy. Implementa una interfaz de clase de servicio, simulando su comportamiento para el código del cliente. Así, en lugar del objeto real, el cliente interactúa con su proxy. Normalmente, todas las solicitudes se pasan a la clase de servicio, pero con acciones adicionales antes o después de su llamada. En pocas palabras, este objeto proxy es una capa entre el código del cliente y el objeto de destino. Veamos un ejemplo de cómo almacenar en caché una solicitud de un disco antiguo muy lento. Sea un horario de tren eléctrico en alguna aplicación antigua, cuyo principio de funcionamiento no se puede cambiar. El disco con el horario actualizado se inserta todos los días a una hora fija. Entonces tenemos:
  1. Interfaz TimetableTrains.
  2. La clase TimetableElectricTrainsque implementa esta interfaz.
  3. Es a través de esta clase que el código del cliente interactúa con el sistema de archivos del disco.
  4. Clase de cliente DisplayTimetable. Su método printTimetable()utiliza métodos de clase TimetableElectricTrains.
El esquema es simple: Patrón de diseño de proxy - 2actualmente, cada vez que se llama a un método, printTimetable()la clase TimetableElectricTrainsaccede al disco, descarga datos y se los proporciona al cliente. Este sistema funciona bien, pero es muy lento. Por lo tanto, se decidió aumentar el rendimiento del sistema agregando un mecanismo de almacenamiento en caché. Esto se puede hacer usando el patrón Proxy: Patrón de diseño de proxy - 3De esta manera la clase DisplayTimetableni siquiera notará que está interactuando con la clase TimetableElectricTrainsProxyy no con la anterior. La nueva implementación carga la programación una vez al día y, ante solicitudes repetidas, devuelve el objeto ya cargado desde la memoria.

¿Para qué tareas es mejor utilizar Proxy?

Aquí hay algunas situaciones en las que este patrón definitivamente será útil:
  1. Almacenamiento en caché.
  2. La implementación diferida también se conoce como implementación diferida. ¿Por qué cargar un objeto todo a la vez cuando puedes cargarlo según sea necesario?
  3. Solicitudes de registro.
  4. Datos provisionales y controles de acceso.
  5. Lanzamiento de hilos de procesamiento paralelo.
  6. Grabar o contar el historial de una llamada.
También hay otros casos de uso. Al comprender el principio de funcionamiento de este patrón, usted mismo podrá encontrar una aplicación exitosa para él. A primera vista, Adjunto hace lo mismo que Fachada , pero no es así. El Proxy tiene la misma interfaz que el objeto de servicio. Además, no confunda el patrón con Decorador o Adaptador . El Decorador proporciona una interfaz extendida, mientras que el Adaptador proporciona una alternativa.

Ventajas y desventajas

  • + Puede controlar el acceso al objeto de servicio como desee;
  • + Capacidades adicionales para gestionar el ciclo de vida de un objeto de servicio;
  • + Funciona sin objeto de servicio;
  • + Mejora el rendimiento y la seguridad del código.
  • - Existe riesgo de deterioro del rendimiento debido a tratamientos adicionales;
  • - Complica la estructura de las clases del programa.

Patrón sustituto en la práctica.

Implementemos un sistema con usted que lea los horarios de los trenes desde el disco:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Una clase que implementa la interfaz principal:
public class TimetableElectricTrains implements TimetableTrains {

   @Override
   public String[] getTimetable() {
       ArrayList<String> list = new ArrayList<>();
       try {
           Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               list.add(line);
           }
       } catch (IOException e) {
           System.err.println("Error:  " + e);
       }
       return list.toArray(new String[list.size()]);
   }

   @Override
   public String getTrainDepartureTime(String trainId) {
       String[] timetable = getTimetable();
       for(int i = 0; i<timetable.length; i++) {
           if(timetable[i].startsWith(trainId+";")) return timetable[i];
       }
       return "";
   }
}
Cada vez que intentas obtener el horario de todos los trenes, el programa lee el archivo del disco. Pero éstas siguen siendo flores. ¡El archivo también se lee cada vez que necesitas obtener el horario de un solo tren! Es bueno que ese código solo exista en malos ejemplos :) Clase de cliente:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       for(int i = 0; i < timetable.length; i++) {
           tmpArr = timetable[i].split(";");
           System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
       }
   }
}
Archivo de ejemplo:

9B-6854;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Probemos:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Conclusión:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Ahora veamos los pasos para implementar nuestro patrón:
  1. Defina una interfaz que le permita utilizar un nuevo proxy en lugar del objeto original. En nuestro ejemplo es TimetableTrains.

  2. Crea una clase de proxy. Debe contener una referencia a un objeto de servicio (creado en una clase o pasado en un constructor);

    Aquí está nuestra clase de proxy:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный un objeto
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    En esta etapa, simplemente creamos una clase con una referencia al objeto original y le pasamos todas las llamadas.

  3. Implementamos la lógica de la clase proxy. Básicamente, la llamada siempre se redirige al objeto original.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный un objeto
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    El método getTimetable()comprueba si la matriz de programación está almacenada en la memoria caché. De lo contrario, emite una solicitud para cargar los datos desde el disco y almacena el resultado. Si la solicitud ya se está ejecutando, devolverá rápidamente un objeto de la memoria.

    Gracias a su sencilla funcionalidad, el método getTrainDepartireTime() no tuvo que ser redirigido al objeto original. Simplemente duplicamos su funcionalidad en un nuevo método.

    No puedes hacer eso. Si tuviste que duplicar código o realizar manipulaciones similares, significa que algo salió mal y debes mirar el problema desde un ángulo diferente. En nuestro ejemplo simple no hay otra manera, pero en proyectos reales, lo más probable es que el código se escriba de manera más correcta.

  4. Reemplace la creación del objeto original en el código del cliente con un objeto de reemplazo:

    public class DisplayTimetable {
       // Измененная enlace
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           for(int i = 0; i<timetable.length; i++) {
               tmpArr = timetable[i].split(";");
               System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
           }
       }
    }

    Examen

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Genial, funciona correctamente.

    También puede considerar una fábrica que creará tanto el objeto original como un objeto de reemplazo dependiendo de ciertas condiciones.

Enlace útil en lugar de un punto

  1. Excelente artículo sobre patrones y un poco sobre “Deputy”

¡Eso es todo por hoy! Sería bueno volver a aprender y poner a prueba tus nuevos conocimientos en la práctica :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION