¿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:- Interfaz
TimetableTrains
. - La clase
TimetableElectricTrains
que implementa esta interfaz. - Es a través de esta clase que el código del cliente interactúa con el sistema de archivos del disco.
- Clase de cliente
DisplayTimetable
. Su métodoprintTimetable()
utiliza métodos de claseTimetableElectricTrains
.
printTimetable()
la clase TimetableElectricTrains
accede 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: De esta manera la clase DisplayTimetable
ni siquiera notará que está interactuando con la clase TimetableElectricTrainsProxy
y 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:- Almacenamiento en caché.
- 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?
- Solicitudes de registro.
- Datos provisionales y controles de acceso.
- Lanzamiento de hilos de procesamiento paralelo.
- Grabar o contar el historial de una llamada.
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:
-
Defina una interfaz que le permita utilizar un nuevo proxy en lugar del objeto original. En nuestro ejemplo es
TimetableTrains
. -
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.
-
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.
-
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.
GO TO FULL VERSION