cv.cm

Programación · Patrones de diseño

Patrón Proxy explicado con ejemplos

6 min de lectura

Respuesta corta

Proxy es un patrón de diseño estructural que ofrece un objeto sustituto con la misma interfaz que un objeto real, de modo que el proxy puede controlar el acceso a él. El cliente habla con el proxy como si fuera el objeto real, y el proxy decide cuándo y cómo reenviar cada llamada. Los tipos más comunes son los proxies virtuales (creación diferida), los proxies de protección (comprobaciones de acceso), los proxies de caché y los proxies remotos.

Intención y el problema que resuelve

Intención: proporcionar un sustituto o marcador de posición de otro objeto para controlar el acceso a él. Proxy es uno de los patrones estructurales del libro de la Banda de los Cuatro (Gang of Four, GoF).

Supón que una aplicación llama a un servicio meteorológico por la red. Cada llamada tarda cientos de milisegundos y cuesta dinero, y la misma ciudad se solicita muchas veces por minuto. No puedes cambiar la clase del servicio y no quieres añadir código de caché en cada lugar que lo llama. Un proxy lo resuelve: implementa la misma interfaz, mantiene una caché y solo llama al servicio real cuando la caché no tiene el dato. Los llamadores no cambian en absoluto.

La misma idea cubre otras necesidades: crear un objeto costoso solo la primera vez que se usa, comprobar permisos antes de una llamada, registrar cada llamada u ocultar que el objeto real vive en otra máquina.

Estructura y participantes

RolResponsabilidad
SujetoLa interfaz compartida por el objeto real y el proxy, para que el cliente pueda usar cualquiera de los dos
Sujeto realEl objeto que hace el trabajo de verdad
ProxyImplementa la interfaz del sujeto, guarda o crea una referencia al sujeto real y añade lógica de control antes o después de reenviar
ClienteTrabaja solo con la interfaz del sujeto y no sabe si tiene un proxy
Tipo de proxyQué controla
Proxy virtualRetrasa la creación de un objeto costoso hasta que se necesita
Proxy de protecciónComprueba si quien llama tiene permiso para realizar una operación
Proxy de cachéAlmacena resultados y los devuelve ante peticiones repetidas
Proxy remotoRepresenta un objeto de otro proceso o máquina y se encarga de la comunicación
Proxy de registroRegistra llamadas y argumentos sin cambiar el objeto real

Implementación en TypeScript: un proxy de caché

WeatherService es el sujeto. CachedWeatherService es el proxy: tiene el mismo método y reenvía al servicio real solo cuando el valor en caché falta o es demasiado antiguo.

interface WeatherService {
  getTemperature(city: string): Promise<number>;
}
class RemoteWeatherService implements WeatherService {
  calls = 0;
  async getTemperature(city: string): Promise<number> {
    this.calls++; // imagine a slow HTTP request here
    return city.length * 3;
  }
}
class CachedWeatherService implements WeatherService {
  private cache = new Map<string, { value: number; time: number }>();
  constructor(private real: WeatherService, private maxAgeMs = 60_000) {}
  async getTemperature(city: string): Promise<number> {
    const hit = this.cache.get(city);
    if (hit && Date.now() - hit.time < this.maxAgeMs) return hit.value;
    const value = await this.real.getTemperature(city);
    this.cache.set(city, { value, time: Date.now() });
    return value;
  }
}
async function main() {
  const real = new RemoteWeatherService();
  const weather: WeatherService = new CachedWeatherService(real);
  await weather.getTemperature("Paris");
  await weather.getTemperature("Paris");
  console.log(await weather.getTemperature("Hanoi"), "real calls:", real.calls);
  // 15 real calls: 2
}
main();

La variable cliente está tipada como WeatherService, así que puedes cambiar entre el servicio real y el proxy en un solo lugar, por ejemplo en una configuración de inyección de dependencias.

Un proxy virtual y de protección con el Proxy de JavaScript

JavaScript tiene un objeto Proxy integrado (ES2015) que intercepta operaciones como lecturas y escrituras de propiedades y llamadas a funciones mediante funciones manejadoras llamadas traps. Es una forma cómoda de construir un proxy sin escribir una clase para cada interfaz.

type Account = { owner: string; balance: number };
function readOnly<T extends object>(target: T): T {
  return new Proxy(target, {
    set(_obj, prop) {
      throw new Error("Cannot change " + String(prop) + ": read-only");
    },
  });
}
const account = readOnly<Account>({ owner: "Ana", balance: 100 });
console.log(account.balance); // 100
try {
  account.balance = 0;
} catch (e) {
  console.log((e as Error).message); // Cannot change balance: read-only
}

Cuándo usarlo y cuándo no

  • Úsalo para añadir carga diferida, control de acceso, caché, registro o acceso remoto sin cambiar la clase real ni a sus llamadores.
  • Úsalo cuando el objeto real esté fuera de tu control, como una clase de una biblioteca o un cliente de red.
  • No lo uses cuando baste un envoltorio de función simple. Para una sola función, una función de orden superior como memoize(fn) es más sencilla que una clase.
  • No escondas llamadas remotas lentas o que fallan tras un proxy que parece local. Los llamadores siguen necesitando manejar la latencia y los errores, así que hazlo visible en la interfaz (por ejemplo, devolviendo una Promise).
  • Ten cuidado con la identidad: un proxy es un objeto distinto del sujeto real, así que proxy === real es falso.

Usos en el mundo real

  • Vue 3 construye su sistema de reactividad sobre el Proxy de JavaScript: leer una propiedad registra una dependencia y escribirla dispara las actualizaciones.
  • Java ofrece java.lang.reflect.Proxy para crear clases proxy dinámicas en tiempo de ejecución; los frameworks lo usan para añadir transacciones, comprobaciones de seguridad o llamadas remotas alrededor de interfaces.
  • Los mapeadores objeto-relacionales como Hibernate devuelven proxies diferidos para las entidades relacionadas y cargan los datos reales de la base de datos en el primer acceso.
  • Las bibliotecas de llamada a procedimiento remoto generan stubs de cliente que parecen objetos locales pero envían cada llamada por la red.

Patrones relacionados

PatrónEn qué se diferencia de Proxy
DecoratorMisma estructura, distinto objetivo: Decorator añade comportamiento nuevo y suele apilarse; Proxy controla el acceso y normalmente gestiona el ciclo de vida del objeto real.
AdapterAdapter da a un objeto una interfaz distinta; Proxy mantiene exactamente la misma interfaz.
FacadeFacade ofrece una interfaz nueva y más simple para todo un subsistema; Proxy sustituye a un solo objeto.
FlyweightFlyweight comparte objetos para ahorrar memoria; un proxy virtual retrasa la creación de un objeto para ahorrar tiempo y memoria.

Preguntas frecuentes

¿Cuál es la diferencia entre Proxy y Decorator?

Ambos envuelven un objeto con la misma interfaz. Un decorador añade responsabilidades y normalmente lo compone el cliente, mientras que un proxy controla el acceso al objeto real y a menudo crea o gestiona ese objeto por sí mismo.

¿El objeto Proxy de JavaScript es lo mismo que el patrón Proxy?

Es una característica del lenguaje que facilita implementar el patrón. Un Proxy de JavaScript intercepta operaciones sobre un objeto destino mediante traps, que es exactamente lo que hace un proxy en el sentido de GoF.

¿Qué es un proxy virtual?

Un proxy virtual sustituye a un objeto que es costoso de crear, como una imagen grande. Crea el objeto real solo cuando se llama por primera vez a un método que lo necesita.

¿Un servidor proxy inverso es un ejemplo del patrón Proxy?

Sigue la misma idea a nivel de red: un servidor como nginx se coloca delante de otros servidores con la misma interfaz HTTP y añade caché, control de acceso o balanceo de carga. El patrón GoF describe la misma idea para objetos dentro de un programa.

Todos los tutoriales de programación