Python 代理模式

代理模式是一种结构型设计模式,它为另一个对象提供一个替身或占位符,以控制对这个对象的访问。简单来说,代理就是一个中间人,它在客户端和目标对象之间起到中介作用。

生活中的代理比喻

想象一下现实生活中的场景:

  • 房产中介:你不需要直接与房主沟通,通过中介就能了解房源信息、安排看房
  • 明星经纪人:厂商想要邀请明星代言,需要通过经纪人来洽谈合作细节
  • 网络代理服务器:你的网络请求先经过代理服务器,再由代理服务器转发到目标网站

在这些例子中,中介/经纪人就是代理,它们控制着对真实对象的访问。


代理模式的核心组成

代理模式通常包含三个关键角色:

1. 抽象主题(Subject)

定义真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。

2. 真实主题(Real Subject)

定义代理所代表的真实对象,是最终要引用的对象。

3. 代理(Proxy)

保存一个引用使得代理可以访问实体,提供一个与真实主题相同的接口,控制对真实主题的访问。


代理模式的类型

代理模式有多种变体,每种都有不同的应用场景:

1. 虚拟代理(Virtual Proxy)

延迟创建开销很大的对象,直到真正需要时才创建。

2. 保护代理(Protection Proxy)

控制对原始对象的访问,用于对象应该有不同的访问权限时。

3. 远程代理(Remote Proxy)

为一个位于不同地址空间的对象提供本地代表。

4. 智能引用代理(Smart Reference Proxy)

在访问对象时执行额外的操作,如引用计数、懒加载等。


Python 实现代理模式

让我们通过具体的代码示例来理解代理模式的实现。

基础接口定义

首先,我们定义抽象主题接口:

实例

from abc import ABC, abstractmethod
from typing import Any

class Subject(ABC):
    """
    抽象主题接口,定义真实主题和代理的共同操作
    """

   
    @abstractmethod
    def request(self) -> Any:
        """执行请求的主要方法"""
        pass

真实主题实现

实例

class RealSubject(Subject):
    """
    真实主题类,包含核心业务逻辑
    通常创建和初始化成本较高
    """

   
    def __init__(self, name: str):
        self.name = name
        print(f"创建 RealSubject 实例: {self.name} (这是一个昂贵的操作)")
   
    def request(self) -> str:
        """执行真实请求"""
        print(f"RealSubject: 处理请求 - {self.name}")
        return f"来自 {self.name} 的响应"

代理类实现

实例

class Proxy(Subject):
    """
    代理类,控制对真实主题的访问
    可以添加额外的功能,如懒加载、访问控制等
    """

   
    def __init__(self, subject_name: str):
        self.subject_name = subject_name
        self._real_subject = None  # 延迟初始化
   
    def _lazy_init(self) -> None:
        """懒加载真实主题对象"""
        if self._real_subject is None:
            self._real_subject = RealSubject(self.subject_name)
   
    def request(self) -> str:
        """代理的请求方法,可以添加额外逻辑"""
        print("Proxy: 在调用真实对象前进行预处理")
       
        # 懒加载真实对象
        self._lazy_init()
       
        # 调用真实对象的方法
        result = self._real_subject.request()
       
        print("Proxy: 在调用真实对象后进行后处理")
        return f"代理增强的结果: {result}"

客户端代码

实例

def client_code(subject: Subject) -> None:
    """
    客户端代码,通过抽象接口与主题交互
    不知道也不关心使用的是真实主题还是代理
    """

    print("客户端: 开始执行操作")
    result = subject.request()
    print(f"客户端: 收到结果 - {result}")
    print()

# 使用示例
if __name__ == "__main__":
    print("直接使用真实主题:")
    real_subject = RealSubject("真实对象A")
    client_code(real_subject)
   
    print("使用代理:")
    proxy = Proxy("代理对象B")
    client_code(proxy)
   
    # 再次使用同一个代理,观察懒加载效果
    print("再次使用同一个代理:")
    client_code(proxy)

运行上述代码,你会看到以下输出:

直接使用真实主题:
创建 RealSubject 实例: 真实对象A (这是一个昂贵的操作)
客户端: 开始执行操作
RealSubject: 处理请求 - 真实对象A
客户端: 收到结果 - 来自 真实对象A 的响应

使用代理:
客户端: 开始执行操作
Proxy: 在调用真实对象前进行预处理
创建 RealSubject 实例: 代理对象B (这是一个昂贵的操作)
RealSubject: 处理请求 - 代理对象B
Proxy: 在调用真实对象后进行后处理
客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应

再次使用同一个代理:
客户端: 开始执行操作
Proxy: 在调用真实对象前进行预处理
RealSubject: 处理请求 - 代理对象B
Proxy: 在调用真实对象后进行后处理
客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应

实际应用示例:图片加载代理

让我们看一个更贴近实际的例子——图片加载的虚拟代理。

图片加载系统

实例

from pathlib import Path
import time

class Image:
    """真实图片类,模拟加载大图片的昂贵操作"""
   
    def __init__(self, filename: str):
        self.filename = filename
        self._load_image()
   
    def _load_image(self) -> None:
        """模拟加载大图片的昂贵操作"""
        print(f"正在加载图片: {self.filename} (这可能需要几秒钟...)")
        time.sleep(2)  # 模拟加载时间
        print(f"图片 {self.filename} 加载完成!")
   
    def display(self) -> None:
        """显示图片"""
        print(f"显示图片: {self.filename}")

class ImageProxy:
    """图片代理,实现懒加载"""
   
    def __init__(self, filename: str):
        self.filename = filename
        self._image = None
   
    def display(self) -> None:
        """显示图片,如果需要则先加载"""
        if self._image is None:
            print("代理: 检测到图片未加载,开始懒加载...")
            self._image = Image(self.filename)
        else:
            print("代理: 图片已加载,直接显示")
       
        self._image.display()

# 使用示例
def demo_image_proxy():
    print("创建图片代理 (不会立即加载图片)")
    proxy = ImageProxy("large_photo.jpg")
   
    print("\n第一次调用 display() - 触发懒加载:")
    proxy.display()
   
    print("\n第二次调用 display() - 直接使用已加载的图片:")
    proxy.display()

# 运行示例
demo_image_proxy()

保护代理示例:访问控制

保护代理用于控制对敏感资源的访问。

实例

class SensitiveData:
    """敏感数据类"""
   
    def __init__(self, data: str):
        self.data = data
   
    def read_data(self) -> str:
        """读取敏感数据"""
        return f"敏感数据: {self.data}"

class ProtectionProxy:
    """保护代理,实现访问控制"""
   
    def __init__(self, sensitive_data: SensitiveData, user_role: str):
        self._sensitive_data = sensitive_data
        self.user_role = user_role
   
    def read_data(self) -> str:
        """读取数据,但进行权限检查"""
        if self.user_role != "admin":
            return "错误: 权限不足,只有管理员可以访问敏感数据"
       
        return self._sensitive_data.read_data()

# 使用示例
def demo_protection_proxy():
    sensitive_data = SensitiveData("机密信息: 项目预算为100万元")
   
    # 普通用户尝试访问
    user_proxy = ProtectionProxy(sensitive_data, "user")
    print("普通用户尝试访问:")
    print(user_proxy.read_data())
   
    # 管理员访问
    admin_proxy = ProtectionProxy(sensitive_data, "admin")
    print("\n管理员访问:")
    print(admin_proxy.read_data())

demo_protection_proxy()

代理模式的优缺点

优点

  1. 控制对象访问:代理可以控制客户端如何以及与何时访问真实对象
  2. 懒加载优化:虚拟代理可以延迟创建昂贵对象,提高性能
  3. 增强安全性:保护代理可以添加访问控制逻辑
  4. 开闭原则:可以在不修改客户端代码的情况下引入新的代理
  5. 职责分离:代理可以处理与核心业务逻辑无关的辅助功能

缺点

  1. 增加复杂性:引入新的抽象层,使代码结构更复杂
  2. 响应延迟:代理可能会增加请求的处理时间
  3. 可能过度设计:对于简单场景,使用代理可能显得多余

代理模式的应用场景

代理模式在以下场景中特别有用:

1. 懒加载(Lazy Loading)

当对象创建成本很高,但可能不会立即使用时。

2. 访问控制(Access Control)

需要限制对某些对象的访问权限时。

3. 本地代表(Remote Representation)

为远程对象提供本地接口,如 RPC 调用。

4. 日志记录(Logging)

在方法调用前后添加日志记录。

5. 缓存(Caching)

为昂贵操作的结果提供缓存。


实践练习

为了巩固对代理模式的理解,尝试完成以下练习:

练习 1:缓存代理

创建一个缓存代理,为计算斐波那契数列的函数添加缓存功能:

实例

class FibonacciCalculator:
    """计算斐波那契数列"""
   
    def fibonacci(self, n: int) -> int:
        if n <= 1:
            return n
        return self.fibonacci(n - 1) + self.fibonacci(n - 2)

# 你的任务:实现 FibonacciCacheProxy 类
# 要求:为斐波那契计算添加缓存,避免重复计算

class FibonacciCacheProxy:
    def __init__(self):
        self._calculator = FibonacciCalculator()
        self._cache = {}  # 在这里实现缓存逻辑
   
    def fibonacci(self, n: int) -> int:
        # 实现缓存逻辑
        pass

# 测试代码
def test_cache_proxy():
    proxy = FibonacciCacheProxy()
   
    print("第一次计算 fib(10):")
    result1 = proxy.fibonacci(10)
    print(f"结果: {result1}")
   
    print("第二次计算 fib(10) (应该从缓存获取):")
    result2 = proxy.fibonacci(10)
    print(f"结果: {result2}")
   
    print(f"两次结果相同: {result1 == result2}")

练习 2:日志代理

创建一个日志代理,记录方法的调用信息:

实例

class DatabaseService:
    """数据库服务"""
   
    def query(self, sql: str) -> str:
        return f"执行查询: {sql}"
   
    def update(self, sql: str) -> str:
        return f"执行更新: {sql}"

# 你的任务:实现 LoggingProxy 类
# 要求:记录每个方法的调用时间、参数和结果

class LoggingProxy:
    def __init__(self):
        self._service = DatabaseService()
   
    def query(self, sql: str) -> str:
        # 添加日志记录逻辑
        pass
   
    def update(self, sql: str) -> str:
        # 添加日志记录逻辑
        pass

总结

代理模式是一个强大的设计模式,它通过引入中间层来控制对真实对象的访问。在 Python 中实现代理模式相对简单,主要得益于 Python 的动态特性。

关键要点

  1. 代理是中介:在客户端和真实对象之间充当中间人
  2. 接口一致:代理和真实对象实现相同的接口
  3. 控制访问:代理可以添加额外的控制逻辑,如懒加载、权限检查等
  4. 灵活应用:根据需求选择不同类型的代理