10_代理模式 Proxy Pattern
1. 基本信息
中文名称: 代理模式
英文名称: Proxy Pattern
模式类型: 结构型设计模式
Rust 中常见实现方式: trait + 包装结构体(wrapper)、Deref/DerefMut trait、Box<dyn Trait>
代理模式的核心是:为某个对象提供一个代理对象,通过代理控制对原对象的访问、增强行为或延迟初始化。
在 Rust 中,代理模式通常通过 包装结构体 + trait 实现,允许在访问原对象前后插入逻辑,同时保持类型安全。
2. 模式核心思想
代理模式的核心思想是:
通过代理对象替代原对象进行访问,从而控制访问权限、延迟初始化、增加附加逻辑或保护对象。
代理模式强调:调用方通过统一接口操作对象,但具体操作可能经过代理层处理。
3. 这个模式解决什么问题
代理模式主要解决以下问题:
控制对对象的访问,例如权限检查或安全保护
延迟初始化大对象,提高性能或节省资源
在访问前后增加额外逻辑,例如日志、缓存或网络请求
对原对象进行防护,避免直接操作导致错误或破坏
典型场景:
- 虚拟代理:延迟加载大型对象(图片、数据库连接)
- 远程代理:通过代理对象调用远程服务
- 保护代理:增加访问权限控制
- 智能引用代理:在访问对象时增加统计、缓存或日志
4. 不使用这个模式会怎样
如果不使用代理模式,可能出现:
调用方直接访问原对象,无法控制访问权限
对象过早初始化,占用大量资源
无法在访问过程中增加日志或缓存逻辑
系统中访问逻辑分散,难以维护
5. 传统面向对象中的实现思路
在传统 OOP 中,代理模式通常有三类角色:
Subject:定义统一接口
RealSubject:原始对象,实现真实业务逻辑
Proxy:代理对象,持有 RealSubject,实现 Subject 接口
调用方通过 Proxy 操作对象,Proxy 可以在调用 RealSubject 前后插入逻辑。
结构示意:
Subject
├── request()
Proxy
├── real_subject: RealSubject
└── request() { pre_action(); real_subject.request(); post_action(); }
RealSubject
└── request() { ...业务逻辑... }
6. Rust 中的实现思路
6.1 Rust 中通常怎么实现
在 Rust 中,代理模式通常通过 trait + 包装结构体 来实现:
// 定义接口
trait Subject {
fn request(&self);
}
// 原始对象
struct RealSubject;
impl Subject for RealSubject {
fn request(&self) {
println!("RealSubject 执行请求");
}
}
// 代理对象
struct Proxy {
real: RealSubject,
}
impl Subject for Proxy {
fn request(&self) {
println!("代理前逻辑: 检查或日志");
self.real.request();
println!("代理后逻辑: 统计或缓存");
}
}
fn main() {
let proxy = Proxy { real: RealSubject };
proxy.request();
}
- 调用方只依赖
Subjecttrait - 代理对象在调用前后增加逻辑
- 原始对象无需修改
可以用 Box<dyn Subject> 支持动态分发:
#![allow(unused)]
fn main() {
let subject: Box<dyn Subject> = Box::new(Proxy { real: RealSubject });
subject.request();
}
6.2 和传统 OOP 写法相比有什么不同
- Rust 不使用继承体系,而是 trait + 包装结构体替代接口和代理类。
- 静态分发(泛型)可在编译期确定类型,效率高。
- 动态分发(
Box<dyn Subject>)允许运行时选择代理或真实对象。 - 没有虚函数指针和子类继承复杂性,类型安全。
6.3 Rust 中是否有更自然的替代写法
- 对简单访问控制或日志,可以直接使用闭包包装对象方法,无需显式 Proxy 结构体。
- 对延迟初始化,可以结合
Option<RealSubject>或OnceLock实现虚拟代理。 - 对共享访问,可用
Arc<Mutex<RealSubject>>管理线程安全的代理。
7. Rust 中涉及的语言特性
trait
impl
包装结构体(struct + 内部对象)
Box<dyn Trait>(可选)
所有权和借用
Option / OnceLock(虚拟代理)
Arc / Mutex(共享访问或多线程安全)
8. 性能与工程代价
- 静态分发代理无虚函数开销,性能接近直接调用。
- 动态分发代理(
Box<dyn Trait>)有轻微虚函数调用开销。 - 包装层过多可能增加代码层级,但运行时开销低。
- 延迟初始化可以节省资源,但首次访问需要初始化成本。
9. 典型应用场景
虚拟代理:延迟加载大型对象(图片、数据库连接)
远程代理:调用远程服务
保护代理:权限检查、访问控制
智能引用代理:统计、缓存、日志
多线程共享对象访问控制
10. 和相似模式的区别
- 装饰器模式:装饰器增加功能但接口不变;代理模式可控制访问并插入逻辑,接口可能保持或调整。
- 适配器模式:适配器修改接口以兼容调用方;代理模式保留接口一致性,关注访问控制和附加逻辑。
- 工厂模式:工厂模式负责对象创建;代理模式关注访问和控制对象。
- 策略模式:策略模式提供可替换算法;代理模式提供对对象访问的中间控制。
11. 使用该模式的优点
调用方不直接访问原对象,增加控制和安全性
可在调用前后添加逻辑(日志、缓存、权限)
支持延迟初始化,节省资源
可与 trait 和动态分发结合实现灵活代理
原对象无需修改,保持封装
12. 使用该模式的代价
代理层增加包装结构,增加代码复杂度
动态分发有轻微性能开销
过多代理可能导致维护难度增加
延迟初始化可能增加首次访问延迟
13. 什么时候不应该使用
原对象简单,无需额外访问控制
系统不涉及延迟初始化或附加逻辑
不需要在访问前后增加额外行为
代理层可能导致代码过度复杂
14. 一个简单例子思路
场景: 文件打印系统
- 原始打印机对象
RealPrinter提供print(&str)方法 - Proxy 对象
PrinterProxy包装RealPrinter - 代理在打印前检查权限,在打印后记录日志
- 调用方只依赖
Printertrait
适合练习 Rust 特性:
trait + impl
包装结构体
Box<dyn Trait> 可选动态分发
所有权和借用
15. 总结一句话
代理模式的本质是:通过代理对象控制对原对象的访问,在访问前后增加逻辑或延迟初始化。在 Rust 中,通过 trait + 包装结构体、
Box<dyn Trait>或 Arc/Mutex 实现安全、高效的代理。