CodeGym /Blog Java /Random-ES /patrón de diseño proxy
Autor
Artem Divertitto
Senior Android Developer at United Tech

patrón de diseño proxy

Publicado en el grupo Random-ES
En programación, es importante planificar correctamente la arquitectura de su aplicación. Los patrones de diseño son una forma indispensable de lograr esto. Hoy hablemos de los proxies.

¿Por qué necesitas un proxy?

Este patrón ayuda a resolver problemas asociados con el acceso controlado a un objeto. Puede preguntarse: "¿Por qué necesitamos un acceso controlado?" Veamos un par de situaciones que te ayudarán a descubrir qué es qué.

Ejemplo 1

Imagine que tenemos un proyecto grande con un montón de código antiguo, donde hay una clase responsable de exportar informes desde una base de datos. La clase funciona sincrónicamente. Es decir, todo el sistema está inactivo mientras la base de datos procesa la solicitud. En promedio, se tarda 30 minutos en generar un informe. En consecuencia, el proceso de exportación comienza a las 00:30 y la gerencia recibe el informe por la mañana. Una auditoría reveló que sería mejor poder recibir el informe de inmediato durante el horario comercial normal. La hora de inicio no se puede posponer y el sistema no se puede bloquear mientras espera una respuesta de la base de datos. La solución es cambiar la forma en que funciona el sistema, generando y exportando el informe en un hilo separado. Esta solución permitirá que el sistema funcione como de costumbre y la administración recibirá informes actualizados. Sin embargo, hay un problema: el código actual no se puede reescribir, ya que otras partes del sistema utilizan su funcionalidad. En este caso, podemos usar el patrón de proxy para introducir una clase de proxy intermedia que recibirá solicitudes para exportar informes, registrar la hora de inicio y lanzar un hilo separado. Una vez que se genera el informe, el hilo termina y todos están contentos.

Ejemplo 2

Un equipo de desarrollo está creando un sitio web de eventos. Para obtener datos sobre nuevos eventos, el equipo consulta un servicio de terceros. Una biblioteca privada especial facilita la interacción con el servicio. Durante el desarrollo, se descubre un problema: el sistema de terceros actualiza sus datos una vez al día, pero se le envía una solicitud cada vez que un usuario actualiza una 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 devolver el resultado almacenado en caché a los visitantes a medida que se recargan las páginas, actualizando el caché según sea necesario. En este caso, el patrón de diseño de proxy es una excelente solución que no cambia la funcionalidad existente.

El principio detrás del patrón de diseño.

Para implementar este patrón, debe crear una clase de proxy. Implementa la interfaz de la clase de servicio, imitando su comportamiento para el código del cliente. De esta manera, el cliente interactúa con un proxy en lugar del objeto real. Por regla general, todas las solicitudes se pasan a la clase de servicio, pero con acciones adicionales antes o después. En pocas palabras, un proxy es una capa entre el código del cliente y el objeto de destino. Considere el ejemplo de almacenar en caché los resultados de una consulta de un disco duro antiguo y muy lento. Supongamos que estamos hablando de un horario para trenes eléctricos en alguna aplicación antigua cuya lógica no se puede cambiar. Se inserta un disco con un horario actualizado todos los días a una hora fija. Entonces tenemos:
  1. TrainTimetableinterfaz.
  2. ElectricTrainTimetable, que implementa esta interfaz.
  3. El código del cliente interactúa con el sistema de archivos a través de esta clase.
  4. TimetableDisplayclase de cliente. Su printTimetable()método utiliza los métodos de la ElectricTrainTimetableclase.
El diagrama es simple: Patrón de diseño de proxy: - 2en la actualidad, con cada llamada del printTimetable()método, la ElectricTrainTimetableclase accede al disco, carga los datos y los presenta al cliente. El sistema funciona bien, pero es muy lento. Como resultado, se tomó la decisión de aumentar el rendimiento del sistema agregando un mecanismo de almacenamiento en caché. Esto se puede hacer usando el patrón de proxy: Patrón de diseño de proxy: - 3por lo tanto, la TimetableDisplayclase ni siquiera se da cuenta de que está interactuando con la ElectricTrainTimetableProxyclase en lugar de con la clase anterior. La nueva implementación carga el horario una vez al día. Para solicitudes repetidas, devuelve el objeto previamente cargado de la memoria.

¿Qué tareas son mejores para un proxy?

Aquí hay algunas situaciones en las que este patrón definitivamente será útil:
  1. almacenamiento en caché
  2. Inicialización retrasada o perezosa ¿Por qué cargar un objeto de inmediato si puede cargarlo según sea necesario?
  3. Solicitudes de registro
  4. Verificación intermedia de datos y acceso
  5. Inicio de subprocesos de trabajo
  6. Grabar el acceso a un objeto
Y también hay otros casos de uso. Al comprender el principio detrás de este patrón, puede identificar situaciones en las que se puede aplicar con éxito. A primera vista, un proxy hace lo mismo que una fachada , pero no es así. Un proxy tiene la misma interfaz que el objeto de servicio. Además, no confunda este patrón con los patrones de decorador o adaptador . Un decorador proporciona una interfaz extendida y un adaptador proporciona una interfaz alternativa.

Ventajas y desventajas

  • + Puede controlar el acceso al objeto de servicio como desee
  • + Habilidades adicionales relacionadas con la gestión del ciclo de vida del objeto de servicio
  • + Funciona sin un objeto de servicio
  • + Mejora el rendimiento y la seguridad del código.
  • - Existe el riesgo de que el rendimiento empeore debido a solicitudes adicionales
  • - Hace que la jerarquía de clases sea más complicada.

El patrón proxy en la práctica

Implementemos un sistema que lea los horarios de los trenes desde un disco duro:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Aquí está la clase que implementa la interfaz principal:

public class ElectricTrainTimetable implements TrainTimetable {

   @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 obtiene el horario del tren, el programa lee un archivo del disco. Pero eso es solo el comienzo de nuestros problemas. ¡El archivo completo se lee cada vez que obtiene el horario incluso para un solo tren! Es bueno que dicho código exista solo en ejemplos de lo que no se debe hacer :) Clase de cliente:

public class TimetableDisplay {
   private TrainTimetable trainTimetable = new ElectricTrainTimetable();

   public void printTimetable() {
       String[] timetable = trainTimetable.getTimetable();
       String[] tmpArr;
       System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
       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;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
Probemos:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Producción:

Train  From  To  Departure time  Arrival time  Travel time
9B-6854  London  Prague  13:43  21:15  07:32
BA-1404  Paris  Graz  14:25  21:25  07:00
9B-8710  Prague  Vienna  04:48  08:49  04:01
9B-8122  Prague  Graz  04:48  08:49  04:01
Ahora veamos los pasos necesarios para presentar nuestro patrón:
  1. Defina una interfaz que permita el uso de un proxy en lugar del objeto original. En nuestro ejemplo, esto es TrainTimetable.

  2. Cree la clase de proxy. Debe tener una referencia al objeto de servicio (créelo en la clase o páselo al constructor).

    Aquí está nuestra clase de proxy:

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
      
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return trainTimetable.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return trainTimetable.getTrainDepartureTime(trainId);
       }
      
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    En esta etapa, simplemente estamos creando una clase con una referencia al objeto original y reenviando todas las llamadas a este.

  3. Implementemos la lógica de la clase proxy. Básicamente, las llamadas siempre se redirigen al objeto original.

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           for (int i = 0; i < timetableCache.length; i++) {
               if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    El getTimetable()comprueba si la matriz de horarios se ha almacenado en la memoria caché. Si no, envía una solicitud para cargar los datos del disco y guarda el resultado. Si ya se ha solicitado el horario, devuelve rápidamente el objeto de la memoria.

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

    No hagas esto. Si tiene que duplicar el código o hacer algo similar, entonces algo salió mal y necesita ver el problema nuevamente desde un ángulo diferente. En nuestro ejemplo simple, no teníamos otra opción. Pero en proyectos reales, lo más probable es que el código se escriba de forma más correcta.

  4. En el código del cliente, cree un objeto proxy en lugar del objeto original:

    
    public class TimetableDisplay {
       // Changed reference
       private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy();
    
       public void printTimetable() {
           String[] timetable = trainTimetable.getTimetable();
           String[] tmpArr;
           System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
           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]);
           }
       }
    }
    

    Controlar

    
    Train  From  To  Departure time  Arrival time  Travel time
    9B-6854  London  Prague  13:43  21:15  07:32
    BA-1404  Paris  Graz  14:25  21:25  07:00
    9B-8710  Prague  Vienna  04:48  08:49  04:01
    9B-8122  Prague  Graz  04:48  08:49  04:01
    

    Genial, funciona correctamente.

    También podría considerar la opción de una fábrica que crea tanto un objeto original como un objeto proxy, dependiendo de ciertas condiciones.

Antes de despedirnos, aquí hay un enlace útil

¡Eso es todo por hoy! No sería una mala idea volver a las lecciones y probar tus nuevos conocimientos en la práctica :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION