代理模式(Proxy Pattern)
代理模式是一种结构型设计模式 ,它为某个对象提供一个代理,以控制对该对象的访问。代理模式可以在不改变原始对象的情况下,通过引入代理对象来扩展功能或控制对原始对象的访问。
核心思想
代理模式的核心思想是通过一个代理对象来代替直接操作目标对象,从而实现对目标对象的访问控制、延迟加载、权限管理等功能。代理对象通常与目标对象实现相同的接口,因此客户端代码可以像使用目标对象一样使用代理对象,而无需感知代理的存在。
适用于需要对对象的访问进行控制或增强的场景。
通过合理使用代理模式,可以在不改变原有对象的基础上,灵活地扩展功能并提升系统的可维护性。
代理模式的分类
根据用途,代理模式可以分为以下几种类型:
远程代理(Remote Proxy)
为一个位于不同地址空间的对象提供本地代理,隐藏远程通信的细节。例如,Java RMI(远程方法调用)就是一种远程代理的实现。
虚拟代理(Virtual Proxy)
延迟创建开销较大的对象,直到真正需要时才初始化。例如,图片加载时先显示占位符,待图片下载完成后替换。
保护代理(Protection Proxy)
控制对目标对象的访问权限。例如,只有特定用户才能访问某些资源。
智能引用代理(Smart Reference Proxy)
在访问目标对象时附加额外的操作,例如引用计数、缓存、日志记录等。
代理模式的组成
Subject(抽象主题)
定义了目标对象和代理对象的共同接口,这样代理对象可以用来替代目标对象。
RealSubject(真实主题)
实现了抽象主题接口,定义了实际的业务逻辑。
Proxy(代理)
持有一个对真实主题的引用,并在需要时调用真实主题的方法。代理可以在调用前后添加额外的逻辑,比如权限检查、日志记录、延迟加载等。
Client(客户端)
使用代理对象完成对目标对象的操作。
实现
#include <iostream>
using namespace std;class VideoSite
{
public:virtual void freeMovie() = 0;virtual void vipMovie() = 0;virtual void ticketMovie() = 0;
};class FixBugVideoSite :public VideoSite
{
public:virtual void freeMovie() {cout << "free Movie" << endl;}virtual void vipMovie() {cout << "vip Movie" << endl;}virtual void ticketMovie() {cout << "ticket movie" << endl;}
};
class FreeVideoSiteProxy :public VideoSite
{
public:FreeVideoSiteProxy() {pVideo = new FixBugVideoSite();}~FreeVideoSiteProxy() { delete pVideo; }virtual void freeMovie(){pVideo->freeMovie();}virtual void vipMovie() {cout<<"error"<<endl;}virtual void ticketMovie() {cout << "error" << endl;}
private:VideoSite* pVideo;
};
class VipVideoSiteProxy :public VideoSite
{
public:VipVideoSiteProxy() {pVideo = new FixBugVideoSite();}~VipVideoSiteProxy() { delete pVideo; }virtual void freeMovie(){pVideo->freeMovie();}virtual void vipMovie() {pVideo->vipMovie();}virtual void ticketMovie() {cout << "error" << endl;}
private:VideoSite* pVideo;
};void watch(VideoSite* p)
{p->freeMovie();p->vipMovie();p->ticketMovie();
}
int main()
{VideoSite* p1 = new FreeVideoSiteProxy();VideoSite* p2 = new VipVideoSiteProxy();watch(p1);watch(p2);std::cout << "Hello World!\n";
}
事例
场景描述
假设我们有一个 Image
类,用于加载和显示图片。由于图片加载可能需要耗费大量资源,我们可以使用虚拟代理来延迟加载图片,直到真正需要显示时才加载。
#include <iostream>
#include <memory> // 使用智能指针管理资源// 抽象主题接口
class Image {
public:virtual void display() = 0; // 显示图片的接口virtual ~Image() = default; // 虚析构函数确保正确释放资源
};// 真实主题类
class RealImage : public Image {
private:std::string fileName;void loadFromDisk() {std::cout << "加载图片: " << fileName << " 从磁盘..." << std::endl;}public:explicit RealImage(const std::string& fileName) : fileName(fileName) {loadFromDisk(); // 模拟图片加载过程}void display() override {std::cout << "显示图片: " << fileName << std::endl;}
};// 代理类
class ProxyImage : public Image {
private:std::unique_ptr<RealImage> realImage; // 使用智能指针管理真实对象std::string fileName;public:explicit ProxyImage(const std::string& fileName) : fileName(fileName), realImage(nullptr) {}void display() override {if (!realImage) {realImage = std::make_unique<RealImage>(fileName); // 延迟加载}realImage->display();}
};// 客户端代码
int main() {// 创建代理对象std::unique_ptr<Image> image1 = std::make_unique<ProxyImage>("photo1.jpg");std::unique_ptr<Image> image2 = std::make_unique<ProxyImage>("photo2.jpg");// 第一次调用 display 时会加载图片std::cout << "第一次调用:" << std::endl;image1->display();// 第二次调用 display 时不会重新加载图片std::cout << "\n第二次调用:" << std::endl;image1->display();// 另一个图片对象std::cout << "\n另一个图片对象:" << std::endl;image2->display();return 0;
}
运行结果
第一次调用:
加载图片: photo1.jpg 从磁盘...
显示图片: photo1.jpg第二次调用:
显示图片: photo1.jpg另一个图片对象:
加载图片: photo2.jpg 从磁盘...
显示图片: photo2.jpg
代码解析
- 抽象主题接口 (
Image
)
- 定义了
display()
方法,作为所有图片对象的统一接口。 - 使用虚析构函数确保子类对象能够被正确释放。
- 真实主题类 (
RealImage
)
- 实现了
display()
方法,负责实际的图片加载和显示。 - 在构造函数中模拟了从磁盘加载图片的过程。
- 代理类 (
ProxyImage
)
- 持有一个指向
RealImage
的智能指针 (std::unique_ptr
)。 - 在第一次调用
display()
时创建RealImage
对象(延迟加载)。 - 后续调用直接复用已创建的
RealImage
对象。
- 客户端代码
- 客户端通过代理对象访问图片,无需感知图片是否已经加载。
代理模式的优点(在 C++ 中体现)
- 资源管理优化
延迟加载减少了不必要的资源消耗,尤其适用于大型文件或高开销对象。 - 代码解耦
客户端代码与真实对象的实现完全解耦,符合开闭原则。 - 功能扩展
可以轻松扩展代理类的功能,例如添加日志记录、权限检查等。