Python代理模式:给对象找个"经纪人"
前言
还记得上次租房子的经历吗?你不需要直接和房东打交道,而是通过房产中介------他们帮你筛选房源、谈价格、签合同,还能在你和房东之间充当缓冲。明星也是如此,所有商演、代言都通过经纪人,粉丝想见面?先过经纪人这一关。
这就是现实生活中的"代理"。在编程世界里,代理模式扮演着同样的角色:为目标对象提供一个替身或占位符,让客户端不直接访问真实对象,而是通过代理来间接访问。
代理能做什么?控制访问权限、延迟加载昂贵资源、添加缓存层、记录日志、计算性能...简单说,就是在不改变原对象的情况下,给它加上各种"增值服务"。
这篇文章会从最简单的保护代理开始,逐步深入到虚拟代理、缓存代理,最后实现一个完整的数据库查询代理系统------集成连接池、缓存、监控于一体的生产级方案。
一、代理模式是什么?
1. 核心思想
想象一下这个场景:你要访问一个大型图片文件,但加载它需要3秒。如果用户打开页面就立即加载所有图片,页面会卡死。怎么办?
传统做法可能是在图片组件里加一堆if判断:
python
if not loaded:
load_image()
if cache_exists:
return from_cache()
if not has_permission:
raise Exception()
这样的代码很快就会变得混乱,而且每个需要这些功能的地方都要重复写一遍。
代理模式提供了更优雅的方案:创建一个代理对象,它和真实对象有相同的接口,但在调用真实对象前后可以加入额外的逻辑。
2. 基本结构
客户端 → 代理对象 → 真实对象
↓
额外功能
(权限、缓存、日志...)
代理和真实对象实现相同的接口,客户端只知道接口,不知道自己用的是代理还是真实对象。
3. 代理的类型
代理模式有很多变体,常见的包括:
| 类型 | 用途 | 典型场景 |
|---|---|---|
| 虚拟代理 | 延迟创建昂贵对象 | 图片懒加载、数据库连接池 |
| 保护代理 | 控制访问权限 | 用户权限验证、敏感数据访问 |
| 远程代理 | 隐藏对象在不同地址空间 | RPC调用、分布式对象 |
| 缓存代理 | 缓存结果,减少重复计算 | API请求缓存、查询缓存 |
| 智能引用 | 添加额外操作 | 引用计数、日志记录 |
本文会重点讲解前四种最常用的代理类型。
二、保护代理:访问控制的门卫
1. 场景描述
假设我们有一个文档系统,不同角色的用户有不同权限:
普通用户:只能查看文档编辑者:可以查看和编辑文档管理员:可以查看、编辑和删除文档
如果在文档类里写一堆权限判断,代码会很乱。保护代理可以帮我们优雅地解决这个问题。
2. 代码实现
首先定义接口和真实对象:
python
from abc import ABC, abstractmethod
from enum import Enum
from typing import Optional
class Role(Enum):
"""用户角色"""
VIEWER = "viewer" # 查看者
EDITOR = "editor" # 编辑者
ADMIN = "admin" # 管理员
class Document(ABC):
"""文档接口"""
@abstractmethod
def view(self) -> str:
pass
@abstractmethod
def edit(self, content: str) -> None:
pass
@abstractmethod
def delete(self) -> None:
pass
class RealDocument(Document):
"""真实文档"""
def __init__(self, title: str, content: str):
self.title = title
self.content = content
self.deleted = False
def view(self) -> str:
if self.deleted:
return "[文档已删除]"
return f"《{self.title}》\n{self.content}"
def edit(self, content: str) -> None:
if self.deleted:
print("[错误] 无法编辑已删除的文档")
return
old_content = self.content
self.content = content
print(f"[编辑] 文档已更新")
print(f" 旧内容: {old_content[:20]}...")
print(f" 新内容: {content[:20]}...")
def delete(self) -> None:
if self.deleted:
print("[错误] 文档已被删除")
return
self.deleted = True
print(f"[删除] 文档《{self.title}》已删除")
然后创建保护代理:
python
class DocumentProxy(Document):
"""文档保护代理 - 控制访问权限"""
def __init__(self, real_document: RealDocument, user_role: Role):
self._real_document = real_document
self._user_role = user_role
def view(self) -> str:
# 所有角色都可以查看
print(f"[权限检查] {self._user_role.value} 正在查看文档")
return self._real_document.view()
def edit(self, content: str) -> None:
# 只有编辑者和管理员可以编辑
if self._user_role in [Role.EDITOR, Role.ADMIN]:
print(f"[权限检查] {self._user_role.value} 有编辑权限")
self._real_document.edit(content)
else:
print(f"[权限拒绝] {self._user_role.value} 无编辑权限")
def delete(self) -> None:
# 只有管理员可以删除
if self._user_role == Role.ADMIN:
print(f"[权限检查] {self._user_role.value} 有删除权限")
self._real_document.delete()
else:
print(f"[权限拒绝] {self._user_role.value} 无删除权限")
3. 使用示例
python
def main():
# 创建真实文档
doc = RealDocument(
title="Python设计模式",
content="代理模式是一种结构型设计模式..."
)
print("=" * 50)
print("场景1: 普通用户")
print("=" * 50)
viewer_proxy = DocumentProxy(doc, Role.VIEWER)
print(viewer_proxy.view())
viewer_proxy.edit("尝试修改内容") # 被拒绝
viewer_proxy.delete() # 被拒绝
print("\n" + "=" * 50)
print("场景2: 编辑者")
print("=" * 50)
editor_proxy = DocumentProxy(doc, Role.EDITOR)
print(editor_proxy.view())
editor_proxy.edit("编辑者修改了内容") # 成功
editor_proxy.delete() # 被拒绝
print("\n" + "=" * 50)
print("场景3: 管理员")
print("=" * 50)
admin_proxy = DocumentProxy(doc, Role.ADMIN)
print(admin_proxy.view())
admin_proxy.edit("管理员修改了内容") # 成功
admin_proxy.delete() # 成功
print(admin_proxy.view()) # 已删除
if __name__ == "__main__":
main()
4. 运行效果
==================================================
场景1: 普通用户
==================================================
[权限检查] viewer 正在查看文档
《Python设计模式》
代理模式是一种结构型设计模式...
[权限拒绝] viewer 无编辑权限
[权限拒绝] viewer 无删除权限
==================================================
场景2: 编辑者
==================================================
[权限检查] editor 正在查看文档
《Python设计模式》
编辑者修改了内容
[权限检查] editor 有编辑权限
[编辑] 文档已更新
旧内容: 代理模式是一种结构型设计模式...
新内容: 编辑者修改了内容...
[权限拒绝] editor 无删除权限
==================================================
场景3: 管理员
==================================================
[权限检查] admin 正在查看文档
《Python设计模式》
编辑者修改了内容
[权限检查] admin 有编辑权限
[编辑] 文档已更新
旧内容: 编辑者修改了内容...
新内容: 管理员修改了内容...
[权限检查] admin 有删除权限
[删除] 文档《Python设计模式》已删除
[权限检查] admin 正在查看文档
[文档已删除]
优势:
- 权限逻辑和业务逻辑分离
- 真实文档类保持简洁,不需要关心权限
- 可以轻松添加新的权限级别
- 代理可以随意组合(可以再套一层日志代理)
三、虚拟代理:延迟加载大对象
1. 场景描述
假设你在开发一个图片浏览器,每张图片有10MB,如果一次性加载100张图片,内存会爆炸。解决方案:只在用户真正查看图片时才加载。
这就是虚拟代理的用武之地。
2. 代码实现
python
import time
from abc import ABC, abstractmethod
from typing import Optional
class Image(ABC):
"""图片接口"""
@abstractmethod
def display(self) -> None:
pass
@abstractmethod
def get_info(self) -> str:
pass
class RealImage(Image):
"""真实图片 - 加载很慢"""
def __init__(self, filename: str):
self.filename = filename
self._load_from_disk()
def _load_from_disk(self) -> None:
"""模拟从磁盘加载图片(耗时操作)"""
print(f"[加载中] 正在从磁盘加载 {self.filename}...")
time.sleep(1) # 模拟耗时操作
print(f"[完成] {self.filename} 加载完成(占用10MB内存)")
self.data = f"<{self.filename}的图像数据>"
def display(self) -> None:
print(f"[显示] {self.filename}")
print(f" 数据: {self.data}")
def get_info(self) -> str:
return f"{self.filename} (已加载)"
class ImageProxy(Image):
"""图片虚拟代理 - 延迟加载"""
def __init__(self, filename: str):
self.filename = filename
self._real_image: Optional[RealImage] = None
def display(self) -> None:
"""只在真正显示时才加载图片"""
if self._real_image is None:
print(f"[代理] 首次显示,开始加载...")
self._real_image = RealImage(self.filename)
else:
print(f"[代理] 使用已加载的图片")
self._real_image.display()
def get_info(self) -> str:
"""获取信息不需要加载图片"""
if self._real_image is None:
return f"{self.filename} (未加载)"
return self._real_image.get_info()
def main():
print("=" * 60)
print("不使用代理:创建时立即加载")
print("=" * 60)
start = time.time()
images = [
RealImage("photo1.jpg"),
RealImage("photo2.jpg"),
RealImage("photo3.jpg"),
]
print(f"\n[统计] 创建3张图片耗时: {time.time() - start:.2f}秒\n")
print("=" * 60)
print("使用代理:创建时不加载,显示时才加载")
print("=" * 60)
start = time.time()
proxy_images = [
ImageProxy("photo4.jpg"),
ImageProxy("photo5.jpg"),
ImageProxy("photo6.jpg"),
]
print(f"\n[统计] 创建3个代理耗时: {time.time() - start:.2f}秒")
print("[说明] 代理创建几乎不耗时!\n")
# 查看图片信息(不触发加载)
print("查看图片列表(不加载图片):")
for proxy in proxy_images:
print(f" - {proxy.get_info()}")
# 只显示用户选择的图片
print("\n用户点击了第2张图片:")
proxy_images[1].display()
print("\n用户再次点击第2张图片:")
proxy_images[1].display()
print("\n用户点击了第3张图片:")
proxy_images[2].display()
if __name__ == "__main__":
main()
3. 运行效果
============================================================
不使用代理:创建时立即加载
============================================================
[加载中] 正在从磁盘加载 photo1.jpg...
[完成] photo1.jpg 加载完成(占用10MB内存)
[加载中] 正在从磁盘加载 photo2.jpg...
[完成] photo2.jpg 加载完成(占用10MB内存)
[加载中] 正在从磁盘加载 photo3.jpg...
[完成] photo3.jpg 加载完成(占用10MB内存)
[统计] 创建3张图片耗时: 3.01秒
============================================================
使用代理:创建时不加载,显示时才加载
============================================================
[统计] 创建3个代理耗时: 0.00秒
[说明] 代理创建几乎不耗时!
查看图片列表(不加载图片):
- photo4.jpg (未加载)
- photo5.jpg (未加载)
- photo6.jpg (未加载)
用户点击了第2张图片:
[代理] 首次显示,开始加载...
[加载中] 正在从磁盘加载 photo5.jpg...
[完成] photo5.jpg 加载完成(占用10MB内存)
[显示] photo5.jpg
数据: <photo5.jpg的图像数据>
用户再次点击第2张图片:
[代理] 使用已加载的图片
[显示] photo5.jpg
数据: <photo5.jpg的图像数据>
用户点击了第3张图片:
[代理] 首次显示,开始加载...
[加载中] 正在从磁盘加载 photo6.jpg...
[完成] photo6.jpg 加载完成(占用10MB内存)
[显示] photo6.jpg
数据: <photo6.jpg的图像数据>
效果对比:
- 不使用代理:创建3张图片需要3秒,占用30MB内存
- 使用代理:创建瞬间完成,只在需要时加载,节省内存和时间
四、缓存代理:让API飞起来
1. 场景描述
你在调用第三方天气API,每次请求需要500ms,而且有请求次数限制。用户频繁刷新页面会导致:
- 响应慢
- 超过
API调用限制 - 浪费带宽
缓存代理可以完美解决这个问题。
2. 代码实现
python
import time
from abc import ABC, abstractmethod
from typing import Dict, Optional
from datetime import datetime, timedelta
class WeatherAPI(ABC):
"""天气API接口"""
@abstractmethod
def get_weather(self, city: str) -> Dict:
pass
class RealWeatherAPI(WeatherAPI):
"""真实天气API - 调用很慢"""
def __init__(self):
self.request_count = 0
def get_weather(self, city: str) -> Dict:
"""模拟API调用"""
self.request_count += 1
print(f"[API调用] 正在请求 {city} 的天气数据...")
print(f"[统计] 这是第 {self.request_count} 次API调用")
# 模拟网络延迟
time.sleep(0.5)
# 模拟API响应
return {
"city": city,
"temperature": 25,
"weather": "晴天",
"humidity": "60%",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
class WeatherAPIProxy(WeatherAPI):
"""天气API缓存代理"""
def __init__(self, real_api: RealWeatherAPI, cache_duration: int = 300):
"""
Args:
real_api: 真实API对象
cache_duration: 缓存有效期(秒),默认5分钟
"""
self._real_api = real_api
self._cache: Dict[str, Dict] = {}
self._cache_time: Dict[str, datetime] = {}
self._cache_duration = timedelta(seconds=cache_duration)
def get_weather(self, city: str) -> Dict:
"""获取天气,优先使用缓存"""
now = datetime.now()
# 检查缓存是否存在且未过期
if city in self._cache:
cache_time = self._cache_time[city]
if now - cache_time < self._cache_duration:
age = (now - cache_time).seconds
print(f"[缓存命中] 返回 {city} 的缓存数据({age}秒前)")
return self._cache[city]
else:
print(f"[缓存过期] {city} 的缓存已过期,重新请求")
else:
print(f"[缓存未命中] {city} 无缓存,首次请求")
# 缓存不存在或已过期,调用真实API
result = self._real_api.get_weather(city)
# 更新缓存
self._cache[city] = result
self._cache_time[city] = now
print(f"[缓存更新] {city} 的数据已缓存")
return result
def clear_cache(self, city: Optional[str] = None) -> None:
"""清除缓存"""
if city:
if city in self._cache:
del self._cache[city]
del self._cache_time[city]
print(f"[缓存清除] 已清除 {city} 的缓存")
else:
self._cache.clear()
self._cache_time.clear()
print("[缓存清除] 已清除所有缓存")
def get_cache_stats(self) -> Dict:
"""获取缓存统计"""
return {
"cached_cities": list(self._cache.keys()),
"cache_size": len(self._cache),
"total_requests": self._real_api.request_count
}
def main():
# 创建真实API和代理
real_api = RealWeatherAPI()
proxy = WeatherAPIProxy(real_api, cache_duration=10) # 10秒缓存
print("=" * 60)
print("测试缓存代理")
print("=" * 60)
# 第一次请求北京天气
print("\n【第1次】查询北京天气:")
start = time.time()
weather = proxy.get_weather("北京")
print(f" 结果: {weather['weather']}, {weather['temperature']}°C")
print(f" 耗时: {time.time() - start:.2f}秒\n")
# 第二次请求北京天气(应该命中缓存)
print("【第2次】查询北京天气(1秒后):")
time.sleep(1)
start = time.time()
weather = proxy.get_weather("北京")
print(f" 结果: {weather['weather']}, {weather['temperature']}°C")
print(f" 耗时: {time.time() - start:.2f}秒\n")
# 查询其他城市
print("【第3次】查询上海天气:")
start = time.time()
weather = proxy.get_weather("上海")
print(f" 结果: {weather['weather']}, {weather['temperature']}°C")
print(f" 耗时: {time.time() - start:.2f}秒\n")
# 再次查询北京(还在缓存期内)
print("【第4次】查询北京天气(3秒后):")
time.sleep(2)
start = time.time()
weather = proxy.get_weather("北京")
print(f" 结果: {weather['weather']}, {weather['temperature']}°C")
print(f" 耗时: {time.time() - start:.2f}秒\n")
# 显示缓存统计
print("=" * 60)
stats = proxy.get_cache_stats()
print(f"缓存统计:")
print(f" 已缓存城市: {stats['cached_cities']}")
print(f" 缓存数量: {stats['cache_size']}")
print(f" API调用总数: {stats['total_requests']} 次")
print(f" 缓存节省: {4 - stats['total_requests']} 次API调用")
if __name__ == "__main__":
main()
3. 运行效果
============================================================
测试缓存代理
============================================================
【第1次】查询北京天气:
[缓存未命中] 北京 无缓存,首次请求
[API调用] 正在请求 北京 的天气数据...
[统计] 这是第 1 次API调用
[缓存更新] 北京 的数据已缓存
结果: 晴天, 25°C
耗时: 0.50秒
【第2次】查询北京天气(1秒后):
[缓存命中] 返回 北京 的缓存数据(1秒前)
结果: 晴天, 25°C
耗时: 0.00秒
【第3次】查询上海天气:
[缓存未命中] 上海 无缓存,首次请求
[API调用] 正在请求 上海 的天气数据...
[统计] 这是第 2 次API调用
[缓存更新] 上海 的数据已缓存
结果: 晴天, 25°C
耗时: 0.50秒
【第4次】查询北京天气(3秒后):
[缓存命中] 返回 北京 的缓存数据(3秒前)
结果: 晴天, 25°C
耗时: 0.00秒
============================================================
缓存统计:
已缓存城市: ['北京', '上海']
缓存数量: 2
API调用总数: 2 次
缓存节省: 2 次API调用
性能对比:
- 无缓存:4次查询 = 4次
API调用 = 2秒 - 有缓存:4次查询 = 2次
API调用 = 1秒,节省50%时间和请求
五、完整实战:数据库查询代理系统
现在我们来实现一个生产级的数据库代理系统,整合:
- 连接池管理:避免频繁创建连接
- 查询缓存:减少数据库压力
- 性能监控:记录查询耗时
- 错误重试:自动重试失败的查询
这个系统会是一个完整的FastAPI应用,可以直接部署使用。
1. 项目结构
db_proxy/
├── main.py # FastAPI主应用
├── proxies/ # 代理层
│ ├── __init__.py
│ ├── base.py # 代理基类
│ └── db_proxy.py # 数据库代理
├── database/ # 数据库层
│ ├── __init__.py
│ ├── connection.py # 数据库连接
│ └── models.py # 数据模型
└── services/ # 业务服务
├── __init__.py
└── user_service.py # 用户服务
2. 核心代码
代理基类和数据库连接在后面的代码文件中实现,这里展示FastAPI主应用如何使用:
监控效果示例:
python
# 查询会被自动缓存和监控
GET /users?role=admin
# 响应包含性能数据
{
"data": [...],
"cache_hit": true,
"query_time": 0.001,
"from_cache": true
}
优势总结:
- 透明性:业务代码不知道代理的存在
- 可组合:可以叠加多个代理(缓存→日志→连接池)
- 易测试 :可以用
Mock代理替换真实数据库 - 高性能:缓存大幅减少数据库压力
六、代理模式 vs 相似模式
代理模式经常和其他模式混淆,这里做个对比:
| 特性 | 代理模式 | 适配器模式 | 装饰器模式 |
|---|---|---|---|
| 目的 | 控制访问、延迟加载、缓存 | 接口转换 | 动态添加功能 |
| 接口 | 与真实对象相同 | 转换不兼容的接口 | 与原对象相同 |
| 对象创建 | 代理可以控制对象的创建 | 适配已存在的对象 | 装饰已存在的对象 |
| 典型场景 | 权限控制、虚拟代理、缓存 | 第三方API对接 |
日志、事务、性能统计 |
| 关系 | 代理控制真实对象 | 适配器转换接口 | 装饰器增强功能 |
举例说明:
- 代理:房产中介(控制你和房东的接触)
- 适配器:电源转换器(转换电压接口)
- 装饰器:给手机贴膜(增加保护功能)
何时用代理:
- 需要控制对象访问
- 对象创建成本高,需要延迟加载
- 需要在不修改原代码的情况下添加缓存
- 需要记录对象的使用情况
何时不用代理:
- 只是简单调用,不需要额外控制
- 接口不兼容(用适配器)
- 要动态叠加多个功能(用装饰器)
七、最佳实践
1. 代理应该对客户端透明
python
# ✅ 好的做法:客户端不知道代理的存在
def get_user_service() -> UserRepository:
real_repo = DatabaseRepository()
return CacheProxy(real_repo) # 返回代理
service = get_user_service()
user = service.get_user(1) # 客户端不知道用的是代理
# ❌ 不好的做法:客户端需要知道代理
proxy = CacheProxy(DatabaseRepository())
if use_cache:
user = proxy.get_user(1)
else:
user = proxy._real_repo.get_user(1) # 直接访问真实对象
2. 代理可以组合使用
python
# 多层代理:缓存 → 日志 → 真实对象
real_api = WeatherAPI()
logged_api = LoggingProxy(real_api)
cached_api = CacheProxy(logged_api)
# 请求会经过:CacheProxy → LoggingProxy → WeatherAPI
weather = cached_api.get_weather("北京")
3. 虚拟代理要考虑线程安全
python
class ThreadSafeImageProxy(Image):
def __init__(self, filename: str):
self.filename = filename
self._real_image: Optional[RealImage] = None
self._lock = threading.Lock()
def display(self) -> None:
if self._real_image is None:
with self._lock: # 双重检查锁定
if self._real_image is None:
self._real_image = RealImage(self.filename)
self._real_image.display()
4. 缓存代理要设置合理的过期策略
python
class SmartCacheProxy:
def __init__(self):
self._cache = {}
self._access_count = {} # 访问计数
self._max_size = 100 # 最大缓存数
def get(self, key):
if key in self._cache:
self._access_count[key] += 1
return self._cache[key]
# 缓存满了,淘汰最少使用的
if len(self._cache) >= self._max_size:
least_used = min(self._access_count, key=self._access_count.get)
del self._cache[least_used]
del self._access_count[least_used]
# 从真实对象获取
result = self._real_object.get(key)
self._cache[key] = result
self._access_count[key] = 1
return result
八、总结
代理模式就像现实中的经纪人,为对象提供"增值服务":
核心价值:
- 访问控制:保护代理控制谁能访问对象
- 延迟初始化:虚拟代理避免昂贵对象的提前创建
- 性能优化:缓存代理减少重复计算和网络请求
- 附加功能:不修改原对象就能添加日志、监控等
适用场景:
- 对象创建成本高(大文件、数据库连接)
- 需要权限控制(敏感数据访问)
- 需要缓存结果(
API调用、复杂查询) - 需要记录使用情况(日志、性能监控)
实现要点:
- 代理和真实对象实现相同接口
- 代理持有真实对象的引用
- 代理可以在调用前后添加额外逻辑
- 对客户端透明,客户端不知道使用的是代理
下次当你需要在不修改原有代码的情况下,为对象添加访问控制、延迟加载或缓存功能时,想想代理模式------给你的对象找个"经纪人",让它更专业、更高效!
如果这篇文章对你有帮助,请给我三连支持:
- 点赞 👍 - 让更多人看到这篇文章
- 收藏 ⭐ - 方便以后查阅
- 分享 📤 - 帮助更多需要的人
你的支持是我创作的最大动力!
也欢迎在评论区分享你使用代理模式的经验,或者提出你的疑问,我会认真回复每一条评论。
作者:山沐与山
本文版权归作者所有,欢迎转载,但请注明出处。