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
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} 的响应"
"""
真实主题类,包含核心业务逻辑
通常创建和初始化成本较高
"""
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 __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)
"""
客户端代码,通过抽象接口与主题交互
不知道也不关心使用的是真实主题还是代理
"""
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()
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()
"""敏感数据类"""
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. 懒加载(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}")
"""计算斐波那契数列"""
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
"""数据库服务"""
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 的动态特性。
关键要点
- 代理是中介:在客户端和真实对象之间充当中间人
- 接口一致:代理和真实对象实现相同的接口
- 控制访问:代理可以添加额外的控制逻辑,如懒加载、权限检查等
- 灵活应用:根据需求选择不同类型的代理
点我分享笔记