Patrón Proxy en Python
El patrón Proxy es un patrón de diseño estructural que proporciona un sustituto o marcador de posición para otro objeto, con el fin de controlar el acceso a este objeto. En pocas palabras, un proxy es un intermediario que actúa como mediador entre el cliente y el objeto objetivo.
Analogía del proxy en la vida real
Imagina un escenario de la vida real:
- Agente inmobiliarioNo necesitas comunicarte directamente con el propietario; a través del agente inmobiliario puedes conocer la información de la propiedad y programar visitas.
- Agente de celebridadesSi una empresa quiere invitar a una celebridad como embajadora de marca, necesita pasar por un agente para negociar los detalles de la cooperación.
- Servidor proxy de redTus solicitudes de red pasan primero por un servidor proxy, y luego el servidor proxy las reenvía al sitio web de destino.
En estos ejemplos, el intermediario/agente es el proxy, y controlan el acceso al objeto real.
Componentes principales del patrón Proxy
El patrón Proxy generalmente incluye tres roles clave:
1. Sujeto abstracto (Subject)
Define una interfaz común entre el sujeto real y el sujeto proxy, de modo que el sujeto proxy pueda usarse en cualquier lugar donde se use el sujeto real.
2. Sujeto Real (Real Subject)
Define el objeto real que representa el proxy; es el objeto al que finalmente se hace referencia.
3. Proxy
Mantiene una referencia que permite al proxy acceder a la entidad real, proporciona una interfaz idéntica a la del sujeto real y controla el acceso al sujeto real.

Tipos de patrones de proxy
El patrón Proxy tiene múltiples variantes, y cada una tiene diferentes escenarios de aplicación:
1. Proxy Virtual (Virtual Proxy)
Retrasa la creación de objetos de alto costo hasta que realmente se necesiten.
2. Proxy de Protección (Protection Proxy)
Controla el acceso al objeto original, utilizado cuando los objetos deben tener diferentes permisos de acceso.
3. Proxy Remoto (Remote Proxy)
Proporciona un representante local para un objeto que se encuentra en un espacio de direcciones diferente.
4. Proxy de Referencia Inteligente (Smart Reference Proxy)
Ejecuta operaciones adicionales al acceder al objeto, como recuento de referencias, carga diferida, etc.
Implementación del patrón Proxy en Python
Entendamos la implementación del patrón Proxy mediante ejemplos de código concretos.
Definición de interfaz base
Primero, definimos la interfaz de tema abstracto:
Ejemplos
from typing import Any
class Subject(ABC):
"""
Interfaz de sujeto abstracto, define las operaciones comunes del sujeto real y el proxy.
"""
@abstractmethod
def request(self) -> Any:
"""Método principal para ejecutar la solicitud"""
pass
Implementación del tema real
Ejemplos
"""
Clase de sujeto real, que contiene la lógica central de negocio.
Normalmente, la creación y la inicialización son costosas.
"""
def __init__(self, name: str):
self.name = name
print(f"Creando instancia de RealSubject: {self.name} (esta es una operación costosa)")
def request(self) -> str:
"""Ejecutar la solicitud real"""
print(f"RealSubject: procesando solicitud - {self.name}")
return f"Respuesta de {self.name}"
Implementación de la clase proxy
Ejemplos
"""
Clase proxy, controla el acceso al sujeto real.
Se pueden añadir funcionalidades adicionales, como carga diferida, control de acceso, etc.
"""
def __init__(self, subject_name: str):
self.subject_name = subject_name
self._real_subject = None # Inicialización diferida
def _lazy_init(self) -> None:
"""Carga diferida del objeto del sujeto real"""
if self._real_subject is None:
self._real_subject = RealSubject(self.subject_name)
def request(self) -> str:
"""Método de solicitud del proxy, se puede añadir lógica adicional"""
print("Proxy: realizar preprocesamiento antes de llamar al objeto real")
# Carga diferida del objeto real
self._lazy_init()
# Llamar al método del objeto real
result = self._real_subject.request()
print("Proxy: realizar posprocesamiento después de llamar al objeto real")
return f"Resultado mejorado por el proxy: {result}"
Código del cliente
Ejemplos
"""
Código del cliente, que interactúa con el sujeto a través de la interfaz abstracta
No sabe ni le importa si se usa el sujeto real o el proxy
"""
print("Cliente: iniciando la operación")
result = subject.request()
print(f"Cliente: resultado recibido - {result}")
print()
# Ejemplo de uso
if __name__ == "__main__":
print("Usando directamente el sujeto real:")
real_subject = RealSubject("Objeto real A")
client_code(real_subject)
print("Usando el proxy:")
proxy = Proxy("Objeto proxy B")
client_code(proxy)
# Usar el mismo proxy de nuevo para observar el efecto de carga diferida
print("Usando el mismo proxy nuevamente:")
client_code(proxy)
Al ejecutar el código anterior, verás la siguiente salida:
直接使用真实主题: 创建 RealSubject 实例: 真实对象A (这是一个昂贵的操作) 客户端: 开始执行操作 RealSubject: 处理请求 - 真实对象A 客户端: 收到结果 - 来自 真实对象A 的响应 使用代理: 客户端: 开始执行操作 Proxy: 在调用真实对象前进行预处理 创建 RealSubject 实例: 代理对象B (这是一个昂贵的操作) RealSubject: 处理请求 - 代理对象B Proxy: 在调用真实对象后进行后处理 客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应 再次使用同一个代理: 客户端: 开始执行操作 Proxy: 在调用真实对象前进行预处理 RealSubject: 处理请求 - 代理对象B Proxy: 在调用真实对象后进行后处理 客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应
Ejemplo de aplicación práctica: proxy de carga de imágenes
Veamos un ejemplo más cercano a la realidad: el proxy virtual para la carga de imágenes.
Sistema de carga de imágenes
Ejemplos
import time
class Image:
"""Clase de imagen real que simula la costosa operación de cargar una imagen grande"""
def __init__(self, filename: str):
self.filename = filename
self._load_image()
def _load_image(self) -> None:
"""Simula la costosa operación de cargar una imagen grande"""
print(f"Cargando imagen: {self.filename} (esto puede tardar unos segundos...)")
time.sleep(2) # Simular el tiempo de carga
print(f"¡Imagen {self.filename} cargada!")
def display(self) -> None:
"""Muestra la imagen"""
print(f"Mostrando imagen: {self.filename}")
class ImageProxy:
Proxy de imagen que implementa carga diferida
def __init__(self, filename: str):
self.filename = filename
self._image = None
def display(self) -> None:
Muestra la imagen; si es necesario, cárgala antes
if self._image is None:
print(Proxy: se ha detectado que la imagen no está cargada; iniciando la carga diferida...)
self._image = Image(self.filename)
else:
print(Proxy: la imagen ya está cargada; se muestra directamente)
self._image.display()
# Ejemplo de uso
def demo_image_proxy():
print(Crear proxy de imagen (no cargará la imagen inmediatamente))
proxy = ImageProxy("large_photo.jpg")
print("\nPrimera llamada a display() - se dispara la carga diferida:)
proxy.display()
print("\nSegunda llamada a display() - se utiliza directamente la imagen ya cargada:)
proxy.display()
# Ejecutar el ejemplo
demo_image_proxy()
Ejemplo de proxy de protección: control de acceso
El proxy de protección se utiliza para controlar el acceso a recursos sensibles.
Ejemplos
Clase de datos sensibles
def __init__(self, data: str):
self.data = data
def read_data(self) -> str:
Leer datos sensibles
return fDatos sensibles: {self.data}
class ProtectionProxy:
Proxy de protección que implementa control de acceso
def __init__(self, sensitive_data: SensitiveData, user_role: str):
self._sensitive_data = sensitive_data
self.user_role = user_role
def read_data(self) -> str:
Leer los datos, pero comprobar los permisos
if self.user_role != "admin":
return Error: permisos insuficientes, solo el administrador puede acceder a los datos sensibles
return self._sensitive_data.read_data()
# Ejemplo de uso
def demo_protection_proxy():
sensitive_data = SensitiveData(Información confidencial: el presupuesto del proyecto es de 1 millón de yuanes)
# El usuario normal intenta acceder
user_proxy = ProtectionProxy(sensitive_data, "user")
print(El usuario normal intenta acceder:)
print(user_proxy.read_data())
# Acceso del administrador
admin_proxy = ProtectionProxy(sensitive_data, "admin")
print("\nAcceso del administrador:)
print(admin_proxy.read_data())
demo_protection_proxy()
Ventajas y desventajas del patrón Proxy
Ventajas
- Control de acceso a objetosEl proxy puede controlar cómo y cuándo el cliente accede al objeto real.
- Optimización de carga diferidaEl proxy virtual puede retrasar la creación de objetos costosos, mejorando el rendimiento.
- Mejorar la seguridad: El proxy de protección puede agregar lógica de control de acceso
- Principio abierto/cerrado: Se pueden introducir nuevos proxies sin modificar el código del cliente
- Separación de responsabilidades: El proxy puede manejar funciones auxiliares no relacionadas con la lógica de negocio principal
Desventajas
- Aumenta la complejidad: Introduce una nueva capa de abstracción, haciendo la estructura del código más compleja
- Latencia de respuesta: El proxy puede aumentar el tiempo de procesamiento de las solicitudes
- Posible sobre-diseño: Para escenarios simples, usar un proxy puede parecer innecesario
Escenarios de aplicación del patrón Proxy
El patrón Proxy es especialmente útil en los siguientes escenarios:
1. Carga diferida (Lazy Loading)
Cuando el costo de crear un objeto es alto, pero es posible que no se use de inmediato.
2. Control de acceso (Access Control)
Cuando se necesita restringir los permisos de acceso a ciertos objetos.
3. Representante local (Remote Representation)
Proporciona una interfaz local para objetos remotos, como llamadas RPC.
4. Registro de logs (Logging)
Agregar registro de logs antes y después de la llamada al método.
5. Caché (Caching)
Proporcionar caché para los resultados de operaciones costosas.
Práctica
Para consolidar la comprensión del patrón Proxy, intenta completar los siguientes ejercicios:
Ejercicio 1: proxy de caché
Crea un proxy de caché que agregue funcionalidad de caché a una función que calcula la secuencia de Fibonacci:
Ejemplos
"""Calcula la secuencia de Fibonacci"""
def fibonacci(self, n: int) -> int:
if n <= 1:
return n
return self.fibonacci(n - 1) + self.fibonacci(n - 2)
# Tu tarea: implementar la clase FibonacciCacheProxy
# Requisito: agregar caché al cálculo de Fibonacci para evitar cálculos repetidos
class FibonacciCacheProxy:
def __init__(self):
self._calculator = FibonacciCalculator()
self._cache = {} # Implementa la lógica de caché aquí
def fibonacci(self, n: int) -> int:
# Implementar la lógica de caché
pass
# Código de prueba
def test_cache_proxy():
proxy = FibonacciCacheProxy()
print("Primer cálculo de fib(10):")
result1 = proxy.fibonacci(10)
print(f"Resultado: {result1}")
print("Segundo cálculo de fib(10) (debería obtenerse de la caché):")
result2 = proxy.fibonacci(10)
print(f"Resultado: {result2}")
print(f"Ambos resultados son iguales: {result1 == result2}")
Ejercicio 2: proxy de logs
Crea un proxy de registro que registre la información de las llamadas a métodos:
Ejemplos
"""Servicio de base de datos"""
def query(self, sql: str) -> str:
return f"Ejecutar consulta: {sql}"
def update(self, sql: str) -> str:
return f"Ejecutar actualización: {sql}"
# Tu tarea: implementar la clase LoggingProxy
# Requisito: registrar el tiempo de llamada, los parámetros y el resultado de cada método
class LoggingProxy:
def __init__(self):
self._service = DatabaseService()
def query(self, sql: str) -> str:
# Agregar lógica de registro de logs
pass
def update(self, sql: str) -> str:
# Agregar lógica de registro de logs
pass
Resumen
El patrón Proxy es un patrón de diseño poderoso que controla el acceso a los objetos reales mediante la introducción de una capa intermedia. Implementar el patrón Proxy en Python es relativamente simple, gracias principalmente a las características dinámicas de Python.
Puntos clave
- El proxy es un intermediario: Actúa como intermediario entre el cliente y el objeto real
- Interfaz consistente: El proxy y el objeto real implementan la misma interfaz
- Control de acceso: El proxy puede agregar lógica de control adicional, como carga diferida, verificación de permisos, etc.
- Aplicación flexible: Elegir diferentes tipos de proxy según los requisitos