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
| Variante | Qué media | Pregunta de diseño |
|---|---|---|
| Virtual Proxy | Creación o carga costosa diferida. | ¿Cuándo se adquiere y quién libera el recurso? |
| Protection Proxy | Acceso según permisos y contexto. | ¿Qué autoridad y operación se comprueban? |
| Remote Proxy | Acceso a una capacidad remota. | ¿Qué fallos y costos debe observar el cliente? |
| Caching Proxy | Reutilización de resultados obtenidos. | ¿Qué resultado sigue siendo válido para quién? |
| Smart Reference | Acciones 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.