代理模式(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++ 中体现)
- 资源管理优化
延迟加载减少了不必要的资源消耗,尤其适用于大型文件或高开销对象。 - 代码解耦
客户端代码与真实对象的实现完全解耦,符合开闭原则。 - 功能扩展
可以轻松扩展代理类的功能,例如添加日志记录、权限检查等。