C++设计模式之结构型模式:代理模式(Proxy)
代理模式(Proxy)是结构型设计模式的一种,它通过提供一个替代对象来控制对原对象的访问,常用于在不改变原对象的前提下,为其添加额外功能(如权限控制、延迟加载、日志记录等)。这种模式类似于现实生活中的“代理人”或“中介”,客户端通过代理与目标对象交互。
一、核心思想与角色
代理模式的核心是“控制访问,增强功能”,通过引入代理对象,在客户端与目标对象之间建立一层中间层。其核心角色如下:
角色名称 | 核心职责 |
---|---|
抽象主题(Subject) | 定义目标对象和代理对象的共同接口,确保代理可替代目标对象。 |
真实主题(RealSubject) | 实现抽象主题接口,是被代理的真实对象,包含核心业务逻辑。 |
代理(Proxy) | 实现抽象主题接口,内部持有真实主题的引用,在调用真实主题方法前后添加额外逻辑(如权限校验)。 |
客户端(Client) | 通过抽象主题接口与代理交互,无需知道代理背后是否是真实对象。 |
核心思想:代理与真实对象实现相同接口,客户端无需区分两者;代理在转发请求的同时,可添加额外操作,实现对真实对象的访问控制和功能增强。
二、实现示例(图片加载代理)
假设我们需要加载大型图片(如高清图片),直接加载可能消耗大量资源且耗时。使用代理模式可实现“延迟加载”(仅在需要显示时才真正加载图片),并添加日志记录功能:
#include <iostream>
#include <string>// 1. 抽象主题(Subject):图片
class Image {
public:virtual void display() const = 0; // 显示图片virtual ~Image() = default;
};// 2. 真实主题(RealSubject):高清图片
class HighResolutionImage : public Image {
private:std::string filename; // 图片文件名// 真实加载图片(耗时操作)void loadFromDisk() const {std::cout << "从磁盘加载高清图片:" << filename << std::endl;}public:HighResolutionImage(const std::string& name) : filename(name) {// 构造函数不加载图片(延迟到display时)}// 显示图片(实际加载并显示)void display() const override {loadFromDisk(); // 真实加载std::cout << "显示高清图片:" << filename << std::endl;}
};// 3. 代理(Proxy):图片代理
class ImageProxy : public Image {
private:HighResolutionImage* realImage; // 真实图片对象(延迟初始化)std::string filename; // 图片文件名std::string userRole; // 用户角色(用于权限控制)// 检查权限bool hasPermission() const {return userRole == "admin" || userRole == "viewer";}public:// 构造函数:仅存储文件名和用户角色,不创建真实对象ImageProxy(const std::string& name, const std::string& role): realImage(nullptr), filename(name), userRole(role) {}// 显示图片:代理控制逻辑void display() const override {// 1. 权限校验if (!hasPermission()) {std::cout << "权限不足:用户" << userRole << "无法查看图片" << std::endl;return;}// 2. 延迟加载:仅在需要时创建真实对象if (!realImage) {realImage = new HighResolutionImage(filename);std::cout << "代理:创建真实图片对象" << std::endl;}// 3. 日志记录std::cout << "代理:记录图片访问 - " << filename << std::endl;// 4. 调用真实对象的方法realImage->display();}// 析构函数:释放真实对象~ImageProxy() override {delete realImage;}
};// 客户端代码:通过代理访问图片
int main() {// 1. 无权限用户访问Image* proxy1 = new ImageProxy("vacation.jpg", "guest");std::cout << "=== 游客访问 ===" << std::endl;proxy1->display();// 2. 有权限用户访问(首次加载)Image* proxy2 = new ImageProxy("vacation.jpg", "viewer");std::cout << "\n=== 查看者首次访问 ===" << std::endl;proxy2->display();// 3. 有权限用户再次访问(复用真实对象)std::cout << "\n=== 查看者再次访问 ===" << std::endl;proxy2->display();// 释放资源delete proxy1;delete proxy2;return 0;
}
三、代码解析
-
抽象主题(Image):定义了
display()
接口,是真实图片和代理的共同基类,确保代理可替代真实对象。 -
真实主题(HighResolutionImage):实现了高清图片的核心功能,
loadFromDisk()
是耗时操作(模拟大型图片加载),display()
负责实际显示。 -
代理(ImageProxy):
- 权限控制:
hasPermission()
检查用户是否有权限访问图片,无权限则拒绝请求。 - 延迟加载:仅在首次调用
display()
且有权限时,才创建HighResolutionImage
对象,避免不必要的资源消耗。 - 日志记录:在调用真实对象方法前记录访问日志。
- 转发请求:最终调用真实对象的
display()
方法,完成核心功能。
- 权限控制:
-
客户端使用:客户端通过
Image
接口与代理交互,无需知道是否使用了代理,实现了对真实对象的透明访问。
四、常见代理类型与适用场景
代理模式有多种变体,根据功能不同可分为:
代理类型 | 核心功能 | 适用场景 |
---|---|---|
远程代理 | 为远程对象(如网络服务)提供本地代理,隐藏网络通信细节。 | 分布式系统、RPC框架 |
虚拟代理 | 延迟创建开销大的对象(如示例中的延迟加载),优化性能。 | 大型图片加载、复杂对象初始化 |
保护代理 | 控制对真实对象的访问,验证权限或过滤请求。 | 权限管理、敏感操作保护 |
智能引用代理 | 在访问对象时添加额外操作(如计数引用、自动释放、日志记录)。 | 资源管理、调试日志 |
缓存代理 | 缓存真实对象的结果,重复请求直接返回缓存,减少计算或网络开销。 | 数据库查询缓存、API结果缓存 |
五、核心优势与与其他模式的区别
优势
- 功能增强:在不修改真实对象的前提下,为其添加额外功能(如日志、权限),符合开闭原则。
- 控制访问:可限制对真实对象的访问(如权限校验),保护敏感资源。
- 优化性能:通过延迟加载、缓存等机制,减少不必要的资源消耗。
- 隐藏细节:如远程代理隐藏网络通信细节,客户端无需关心对象位置。
与其他模式的区别
模式 | 核心差异点 |
---|---|
代理模式 | 控制对对象的访问,添加额外功能,代理与真实对象实现同一接口,强调“访问控制”。 |
装饰器模式 | 动态为对象添加功能,不控制访问,装饰器通常嵌套使用,强调“功能扩展”。 |
适配器模式 | 转换接口使不兼容的类协作,不改变原功能,强调“接口适配”。 |
外观模式 | 为复杂子系统提供简化接口,不针对单个对象,强调“简化使用”。 |
六、实践建议
- 接口一致性:代理必须与真实对象实现相同接口,确保客户端透明使用。
- 单一职责:一个代理应专注于一种功能(如要么做权限控制,要么做缓存),避免设计复杂的“万能代理”。
- 延迟初始化:对开销大的真实对象,通过代理实现延迟创建,优化系统启动速度。
- 避免过度使用:简单场景下直接使用真实对象更高效,无需引入代理增加复杂性。
代理模式的核心价值在于“在不侵入原对象的前提下,灵活控制访问并增强功能”。它通过引入中间层,实现了客户端与真实对象的解耦,同时为系统扩展提供了便利。在需要权限控制、性能优化或功能增强的场景中,代理模式是一种优雅的解决方案。