Proxy Pattern

Intención y problema

Proxy coloca un representante delante de otro objeto para controlar, diferir o mediar su acceso, conservando un contrato compatible para el consumidor. La representación puede atender costo de adquisición, permisos o ubicación. El material GoF de CMU presenta estas motivaciones y los roles Subject, Proxy y Real Subject.

La presión aparece cuando obtener una referencia y utilizar la capacidad no deberían implicar exactamente el mismo trabajo. Un catálogo puede mostrar datos descriptivos de una imagen sin cargar todavía su contenido pesado; un cliente puede necesitar acceder a un recurso cuyo uso exige comprobar contexto.

La indirection solo aporta si conserva una conversación comprensible. Compatible no significa que cualquier costo o fallo pueda esconderse sin consecuencias. El contrato incluye comportamiento observable, además de nombres y tipos.

Fuerzas y estructura

Client → Subject contract
             ↑           ↑
           Proxy      Real Subject
             └──────────→

Subject expresa la capacidad que el cliente necesita. Real Subject realiza la operación efectiva. Proxy representa esa capacidad, decide cómo se accede y delega cuando corresponde. El consumidor puede utilizar el representante sin conocer la implementación concreta que queda detrás.

La tensión principal es conservar facilidad de uso mientras cambian adquisición, acceso o localización. También importa quién posee recursos, cuánto dura la representación y qué sucede si se utiliza desde varios consumidores. Un contrato que calla esas decisiones puede crear sorpresas difíciles de diagnosticar.

En un ejemplo propio, ImageProxy permite consultar dimensiones ya disponibles y carga la imagen al solicitar contenido. Dos solicitudes simultáneas requieren decidir si comparten una adquisición, esperan o pueden repetirla. La elección pertenece al contrato y al contexto; dibujar un Proxy no la resuelve automáticamente.

Variantes y colaboración

VarianteQué mediaPregunta de diseño
Virtual ProxyCreación o carga costosa diferida.¿Cuándo se adquiere y quién libera el recurso?
Protection ProxyAcceso según permisos y contexto.¿Qué autoridad y operación se comprueban?
Remote ProxyAcceso a una capacidad remota.¿Qué fallos y costos debe observar el cliente?
Caching ProxyReutilización de resultados obtenidos.¿Qué resultado sigue siendo válido para quién?
Smart ReferenceAcciones asociadas al uso de una referencia.¿Qué ownership, seguimiento o instrumentación agrega?

Estas categorías no son exhaustivas ni mutuamente exclusivas. ImageProxy podría diferir la carga y reutilizar el contenido adquirido. Eso exige explicar ambas políticas; juntar etiquetas no garantiza que su combinación sea correcta.

La instrumentación puede registrar adquisición, llamada efectiva y fallo, distinguiéndolos del simple acceso al representante. Medir solo duración del método externo puede ocultar qué recurso está causando el costo. Aun así, agregar métricas no basta para identificar Proxy: debe existir una intención de representación o mediación del acceso.

Proxy frente a Decorator y Adapter

decorator-pattern agrega responsabilidades componibles; Proxy representa o controla acceso. Ambos pueden implementar la misma interfaz, envolver otro objeto y delegar llamadas. La misma estructura no determina el mismo patrón.

Un wrapper que agrega formato o transformación seleccionable atiende una presión distinta de un sustituto que difiere adquisición. Algunas implementaciones reúnen ambas intenciones. En ese caso hay que describir responsabilidades, orden y comportamiento observable, en vez de buscar una categoría exclusiva por apariencia.

adapter-pattern traduce un contrato incompatible para hacerlo utilizable. Proxy conserva el contrato esperado mientras media su acceso. No es una regla sintáctica absoluta: firmas iguales pueden ocultar protocolos diferentes, y un representante remoto puede necesitar traducción interna.

La pregunta útil es qué necesita el consumidor: una conversación incompatible traducida o un representante compatible cuyo acceso debe administrarse. structural-patterns compara estas fuerzas con la simplificación de un subsistema.

Remote Proxy y semántica de red

CustomerServiceProxy.GetCustomer puede parecer una llamada normal. Si cruza red, aparecen latencia, fallos parciales, serialización, versiones, autorización y observabilidad entre procesos. Fowler explica por qué la distinción entre llamadas locales y remotas afecta el diseño de interfaces en Microservices and the First Law of Distributed Objects.

Remote Proxy no hace semánticamente equivalentes las llamadas locales y remotas. Ocultar detalles de transporte puede ser útil; presentar cada getter como una operación local barata invita a conversaciones demasiado finas, esperas inesperadas y diagnósticos incorrectos.

Un timeout limita la espera del consumidor, sin demostrar que el servidor canceló trabajo o efectos. Un retry puede repetir una solicitud cuyo resultado se perdió. El proxy debe preservar información suficiente para elegir una política segura, especialmente si la operación modifica estado.

Un circuit-breaker puede controlar admisión ante una dependencia problemática; no convierte un fallo remoto en éxito. Reintentar dentro de cada proxy sin una política de conjunto puede amplificar presión y contribuir a cascading-failures. Los mecanismos distribuidos necesitan límites propios.

La transparencia de ubicación debe tener un alcance explícito: el cliente puede desconocer la dirección concreta y seguir necesitando presupuesto de tiempo, resultados de fallo y contexto de trazas. Versionar el contrato tampoco elimina diferencias de datos o errores entre servidor y representante.

Caching y acceso seguro

Un caching proxy puede ahorrar llamadas, pero debe identificar el resultado y su vigencia. Una clave que omite usuario, permisos, parámetros relevantes o versión puede reutilizar datos incorrectos. La compatibilidad del contrato depende de si admite staleness y de qué consistencia promete.

Invalidación y memory pressure forman parte del costo. Al vencer muchas entradas simultáneamente, varias solicitudes pueden reconstruir el mismo resultado y generar un cache stampede relacionado con thundering-herd. No hace falta resolver aquí caching completo; sí evitar atribuir al wrapper una mejora universal.

Protection Proxy puede comprobar permisos, restringir operaciones y aplicar contexto. Eso no equivale a una arquitectura completa de autorización. Debe analizarse si existe un acceso alternativo al recurso, dónde se impone la política y cómo cambia la autoridad durante la vida del representante.

Guardar una autorización inicial indefinidamente puede ser incorrecto si cambian identidad o permisos. Revalidar cada operación también tiene costo. La política debe corresponder al contrato de seguridad real, sin esconder credenciales ni su renovación como detalle irrelevante.

Consecuencias, lifecycle y pruebas

Proxy puede contener decisiones de acceso y evitar trabajo innecesario. También añade indirection, estado y posibles puntos de fallo. La adquisición diferida desplaza errores desde construcción hacia uso; el consumidor debe saber qué operaciones pueden fallar o bloquear.

Si el Real Subject requiere liberación, hay que definir si la controla el proxy, el consumidor u otro propietario. Reutilizar un representante no demuestra reutilizar de forma segura una conexión o recurso. Identidad, concurrencia y ownership necesitan decisiones separadas.

Las pruebas deben cubrir el comportamiento prometido: adquisición cuando corresponde, denegación antes del efecto, resultado reutilizado dentro de su vigencia y fallo remoto observable. Un mock que siempre responde inmediatamente puede validar delegación y perder precisamente el riesgo que motivó la representación.

Aplicabilidad, alternativas y mal uso

Proxy es candidato cuando controlar adquisición, autorización contextual o acceso remoto resulta una responsabilidad reconocible. pattern-selection pide compararlo con acceso directo y con contratos más explícitos. Una operación declarada como remota puede comunicar mejor sus costos que una simulación de objeto local.

Funciones de acceso, referencias lazy, wrappers generados, delegates o capacidades del lenguaje pueden expresar mediación sin una jerarquía escrita a mano. Elegir generación dinámica no exime de comprender errores, identidad y lifecycle. El mecanismo reduce trabajo mecánico, no decide la política.

Los malos usos incluyen getters con I/O costoso inesperado, cambios silenciosos de semántica, caching sin invalidación y autenticación invisible. Cadenas de proxies con retries, métricas y cachés superpuestas pueden dificultar encontrar quién ejecutó qué y multiplicar efectos.

La revisión final debe poder reconstruir el camino efectivo y sus garantías. Si nadie puede explicar cuándo se adquiere, qué se conserva y cómo falla, el representante está ocultando decisiones necesarias. Su valor depende de controlar acceso de forma comprensible, sin prometer que la red o el costo dejaron de existir.