Design Patterns - Proxy

Propósito

El patrón Proxy brinda un sustituto de un objeto, con el propósito de controlar el acceso, posponer su creación y representación en memoria hasta que realmente se necesite, o realizar acciones adicionales cuando se accede al objeto, como la carga tardía, la validación de permisos y el manejo de referencias remotas.

Podemos verlo como un representante, que hace de intermediario con el objeto real.

Problema

Resuelve varios problemas:

  • Control de Acceso a Objetos: Cuando se necesita controlar o restringir el acceso a un objeto.
  • Costos de Creación de Objetos: En situaciones donde crear un objeto es costoso en términos de recursos o tiempo, y se desea posponer esta creación hasta que sea realmente necesario (carga tardía).
  • Referencias a Objetos Remotos: Facilita el trabajo con objetos que residen en diferentes espacios de memoria o en diferentes servidores.

Solución

La solución que propone este patrón es:

  • Sustitución de Objetos Reales: Actúa como un intermediario entre el cliente y el objeto real, proporcionando una interfaz idéntica al objeto real.
  • Control de Acceso o Funcionalidad Adicional: Puede agregar funcionalidades adicionales, como la carga tardía, la gestión de recursos o la verificación de seguridad, antes o después de reenviar la llamada al objeto real.

Estructura

proxy

Participantes

  • Subject: Interfaz común para el Proxy y el RealSubject.
  • RealSubject: El objeto real que realiza la operación real.
  • Proxy: Representante del RealSubject, controla el acceso a este.
  • Client: Usa el objeto real a través del intermediario.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • se necesita un control de acceso a un objeto.
  • hay que gestionar operaciones costosas que deberían realizarse solo bajo demanda.
  • sea necesario trabajar con referencias remotas o carga tardía.

Ventajas

Separación de Responsabilidades: Permite separar las tareas primarias de un objeto de aspectos secundarios (como control de acceso, logging, etc.).

Mejora del Rendimiento: Puede mejorar el rendimiento y la gestión de recursos mediante la carga tardía y la gestión de la conexión.

Seguridad Mejorada: Permite agregar capas adicionales de seguridad.

Desventajas

Retardo en la Respuesta: El uso de un proxy puede introducir cierta demora en las respuestas.

Complejidad Adicional en el Diseño: Agrega una capa adicional de abstracción, lo que puede complicar el diseño y la depuración.

Posible Sobrecarga de Funcionalidad: Un proxy mal diseñado podría terminar haciendo demasiadas cosas, lo que dificultaría su mantenimiento.

Ejemplo: Módulo de Gestión de Imágenes de Alta Resolución

Debemos crear un módulo de gestión de imágenes para un sistema que trabaja con imágenes de alta resolución.

Estas imágenes son muy pesadas y su carga desde el disco duro o una fuente remota es costosa, en términos de tiempo y recursos del sistema.

Problema

Cargar todas las imágenes de alta resolución en la memoria al iniciar no es eficiente y puede afectar el rendimiento, además es innecesario, ya que solo unas pocas se visualizan a la vez.

Solución planteada

Para optimizar la carga y la visualización de imágenes, vamos usar el patrón Proxy. Esto nos permite diferir la carga de la imagen real hasta que sea absolutamente necesario (por ejemplo, cuando el usuario decide ver una en particular). El Proxy actúa como un marcador de posición, cargando la imagen solo cuando se invoca su método display.

Creamos la interfaz Subject (Image) con el método display. La clase SubjectReal, HighResolutionImage es la que procesa la imagen real. Creamos el intermediario ImageProxy que nos va a permitir efectuar la carga tardía.

proxy

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos la Interfaz Imagen (Subject):


  
  interface Image {
      void display();
  }
              
              

Implementamos un RealSubject:


              
  class HighResolutionImage implements Image {
      public HighResolutionImage(String imageFilePath) {
          // Supongamos que cargar la imagen es una operación costosa
          loadFromDisk(imageFilePath);
      }
  
      private void loadFromDisk(String path) {
          System.out.println("Cargando " + path);
      }
  
      public void display() {
          System.out.println("Mostrar imagen");
      }
  }
              
              

Creamos la clase Proxy:


              
  class ImageProxy implements Image {
      private HighResolutionImage highResImage;
      private String imageFilePath;
  
      public ImageProxy(String imageFilePath) {
          this.imageFilePath = imageFilePath;
      }
  
      public void display() {
          if (highResImage == null) {
              highResImage = new HighResolutionImage(imageFilePath);
          }
          highResImage.display();
      }
  }
  
              

El cliente usa el Proxy:


              
  public class ImageViewer {
      public static void main(String[] args) {
          Image image1 = new ImageProxy("photo1.jpg");
          Image image2 = new ImageProxy("photo2.jpg");
  
          // La imagen no se carga en memoria hasta que se llama a este método
          image1.display();
      }
  }
              
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Subject, RealSubject, Proxy, Client:

  • Image (Subject): Interfaz común para el Proxy y el RealSubject.
  • HighResolutionImage (RealSubject): La imagen real que se carga de forma costosa, implementa Subject.
  • ImageProxy (Proxy): Un sustituto para el RealSubject que controla el acceso a él realizando la carga tardía.
  • ImageViewer (Client): El cliente que interactúa con el Proxy.

Conclusiones

El patrón Proxy es una solución eficiente para gestionar recursos costosos como imágenes de alta resolución en un sistema. Al usar un proxy, podemos cargar recursos pesados solo cuando es necesario, mejorando así el rendimiento y la experiencia del usuario.

Patrones relacionados

  • Adapter

El Adapter brinda una interfaz diferente para el objeto que adapta, el Proxy en cambio proporciona la misma interfaz. 

  • Decorator

Tienen implementación parecida pero distinto propósito. Uno agrega responsabilidades, mientras que el otro actúa como intermediario, controlando el acceso por ejemplo.