Proxy
Un patrón estructural: un objeto que ocupa el lugar de otro y decide cuándo y cómo se accede a él.
Proxy es un patrón estructural que provee un objeto sustituto para controlar el acceso a otro objeto.
El problema
Cada producto de AndesShop tiene varias fotos en alta resolución. Cargar esas imágenes desde el storage en la nube es relativamente lento, y la mayoría de las veces ni siquiera hace falta: en una lista de resultados de búsqueda, se muestran cientos de productos, pero el usuario solo llega a ver en pantalla una fracción de ellos antes de hacer scroll. Cargar las imágenes de los cientos de productos por adelantado, “por las dudas”, desperdicia tiempo y ancho de banda.
La solución
Proxy introduce un objeto que implementa la misma interfaz que el objeto real (ProductImage), pero que retrasa el trabajo costoso hasta el momento en que realmente hace falta. El código cliente interactúa siempre con la misma interfaz, sin necesidad de saber si del otro lado está la imagen real o un sustituto que todavía no la cargó.
classDiagram
class ProductImage {
<<interface>>
+display() String
}
class RealProductImage {
-url String
-bytes byte[]
+display() String
}
class LazyProductImageProxy {
-url String
-real RealProductImage
+display() String
}
class ProductPage {
-image ProductImage
}
ProductImage <|.. RealProductImage
ProductImage <|.. LazyProductImageProxy
LazyProductImageProxy --> "0..1" RealProductImage : subject
LazyProductImageProxy ..> RealProductImage : «create» en el primer display()
ProductPage --> "1" ProductImage : no distingue proxy de realEjemplo en Java
interface ProductImage {
String display();
}
// El objeto real: cargarlo es costoso
class RealProductImage implements ProductImage {
private final String url;
public RealProductImage(String url) {
this.url = url;
loadFromCloudStorage(); // trabajo costoso, ocurre al construir el objeto
}
private void loadFromCloudStorage() {
System.out.println("Loading image from " + url + "...");
}
public String display() {
return "Displaying image: " + url;
}
}
// El Proxy: pospone la carga hasta el primer uso real
class LazyProductImageProxy implements ProductImage {
private final String url;
private RealProductImage real;
public LazyProductImageProxy(String url) {
this.url = url; // liviano: no carga nada todavía
}
public String display() {
if (real == null) {
real = new RealProductImage(url); // recién acá se paga el costo
}
return real.display();
}
}
// Client code: crear el proxy es barato, sin importar cuántos productos haya en la lista
List<ProductImage> searchResults = skus.stream()
.map(sku -> new LazyProductImageProxy(imageUrlFor(sku)))
.toList();
// La imagen real recién se carga cuando el usuario efectivamente la ve
searchResults.get(0).display();
Cuándo usarlo
- Cuando necesitás posponer la creación de un objeto costoso hasta el momento en que realmente se necesita (virtual proxy).
- Cuando necesitás controlar el acceso a un objeto — verificar permisos antes de dejar pasar la llamada (protection proxy), o loguear cada acceso sin modificar el objeto real.
Cuándo evitarlo
Si el objeto no es costoso de crear ni necesita control de acceso, un Proxy solo agrega una capa de indirección sin ningún beneficio real.
Ventajas y desventajas
| Ventajas | Desventajas |
|---|---|
| Permite controlar el objeto real sin que el código cliente lo note | Agrega una capa de indirección que puede complicar el seguimiento del código |
| Puede posponer trabajo costoso hasta que realmente haga falta | La respuesta puede llegar con latencia extra la primera vez que se resuelve el objeto real |
| Funciona aunque el objeto real todavía no exista o esté en otro proceso |
