代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理,以控制对这个对象的访问。代理模式可以在不修改目标对象的情况下,提供额外的功能,例如访问控制、延迟加载、日志记录等。
主要特点
- 控制对象的访问:代理对象可以在客户端和目标对象之间添加一个中间层,用于控制对目标对象的访问。
- 延迟加载:在某些情况下,代理对象可以延迟目标对象的创建和初始化,直到需要使用时才进行。
- 日志记录和监控:代理对象可以在调用目标对象的方法前后添加日志记录或其他监控功能。
- 远程代理:代理对象可以代表一个位于远程的目标对象,使客户端可以透明地访问远程对象。
示例
假设我们有一个图片查看应用,需要在访问大图片时进行延迟加载。我们可以使用代理模式来实现这一需求。
接口和具体类
首先,我们定义一个图片接口和一个具体的图片类。
#include <iostream>
#include <memory>
#include <string>
// 图片接口
class Image {
public:
virtual ~Image() = default;
virtual void display() = 0;
};
// 具体图片类
class RealImage : public Image {
public:
RealImage(const std::string& filename) : filename(filename) {
loadFromDisk();
}
void display() override {
std::cout << "Displaying " << filename << std::endl;
}
private:
void loadFromDisk() {
std::cout << "Loading " << filename << std::endl;
}
std::string filename;
};
代理类
接着,我们定义一个代理类,用于控制对 RealImage
对象的访问。
// 代理图片类
class ProxyImage : public Image {
public:
ProxyImage(const std::string& filename) : filename(filename) {}
void display() override {
if (!realImage) {
realImage = std::make_shared<RealImage>(filename);
}
realImage->display();
}
private:
std::string filename;
std::shared_ptr<RealImage> realImage;
};
主函数测试
最后,我们在主函数中使用代理类来访问图片对象。
int main() {
std::shared_ptr<Image> image = std::make_shared<ProxyImage>("test.jpg");
// 图片将会在第一次显示时加载
std::cout << "First display:" << std::endl;
image->display();
// 图片已经加载,不会再次加载
std::cout << "\nSecond display:" << std::endl;
image->display();
return 0;
}
解释
-
Image:这是一个图片接口,定义了一个纯虚函数
display
,表示显示图片的操作。 -
RealImage:这是具体的图片类,实现了
Image
接口,并在构造函数中加载图片。在display
方法中,显示图片的内容。 -
ProxyImage:这是代理图片类,实现了
Image
接口,并在display
方法中控制对RealImage
对象的访问。只有在第一次调用display
方法时,才会创建并加载RealImage
对象。 -
主函数:
- 创建一个
ProxyImage
对象,并通过它来访问图片。 - 在第一次调用
display
方法时,图片将会被加载并显示。 - 在第二次调用
display
方法时,图片已经加载,不会再次加载。
- 创建一个
总结
代理模式通过为目标对象提供一个代理对象,可以在不修改目标对象的情况下,添加额外的功能,例如控制访问、延迟加载、日志记录等。代理模式增强了系统的灵活性和可扩展性,适用于需要控制对象访问或添加额外功能的场景。
上述示例为虚拟代理,还有另外一种保护代理
代理模式有多种变体,虚拟代理和保护代理是其中的两种常见类型。
虚拟代理(Virtual Proxy)
用途:
虚拟代理用于控制对资源消耗大的对象的访问,通常用于延迟加载(lazy loading)。它在需要时才创建真正的对象,以节省系统资源。
实现方式:
虚拟代理类在第一次访问时创建真实对象,在此之前,它可以提供一些替代行为或占位符行为。一旦真实对象被创建,虚拟代理将所有请求委托给真实对象。
示例:
如上。
保护代理(Protection Proxy)
用途:
保护代理用于控制对对象的访问权限。它可以检查用户的访问权限,在调用目标对象的方法之前进行验证。
实现方式:
保护代理类在调用目标对象的方法之前,首先进行权限检查,只有在权限检查通过的情况下,才会调用目标对象的方法。
示例:
假设我们有一个包含敏感数据的类,我们希望只有授权用户才能访问这些数据。我们可以使用保护代理来实现这一需求。
#include <iostream>
#include <memory>
#include <string>
// 数据接口
class SensitiveData {
public:
virtual ~SensitiveData() = default;
virtual void display() = 0;
};
// 具体数据类
class RealSensitiveData : public SensitiveData {
public:
RealSensitiveData(const std::string& data) : data(data) {}
void display() override {
std::cout << "Sensitive Data: " << data << std::endl;
}
private:
std::string data;
};
// 保护代理类
class ProtectionProxy : public SensitiveData {
public:
ProtectionProxy(const std::string& data, const std::string& user)
: data(std::make_shared<RealSensitiveData>(data)), user(user) {}
void display() override {
if (hasAccess()) {
data->display();
} else {
std::cout << "Access denied for user: " << user << std::endl;
}
}
private:
bool hasAccess() {
// 简单的权限检查逻辑
return user == "admin";
}
std::shared_ptr<SensitiveData> data;
std::string user;
};
// 主函数测试
int main() {
std::shared_ptr<SensitiveData> data = std::make_shared<ProtectionProxy>("Top Secret", "admin");
data->display(); // 输出敏感数据
data = std::make_shared<ProtectionProxy>("Top Secret", "guest");
data->display(); // 输出访问被拒绝
return 0;
}
对比和总结
- 虚拟代理(Virtual Proxy):用于控制资源消耗大的对象的访问,通过延迟加载来优化性能。在对象第一次被访问时才创建真实对象。
- 保护代理(Protection Proxy):用于控制对对象的访问权限,通过在调用目标对象的方法之前进行权限检查来保护对象。
尽管虚拟代理和保护代理都是代理模式的变体,它们的用途和实现方式不同,虚拟代理主要解决性能问题,而保护代理主要解决安全问题。根据具体的需求,选择合适的代理类型可以有效地提高系统的性能和安全性。
动态代理
动态代理是一种在运行时创建代理对象的技术,与静态代理相比,它无需在编译时明确指定代理类,而是可以在运行时动态生成代理类。这在许多编程语言中都有实现,例如Java、C#和Python。
- 减少冗余代码:不需要为每个接口创建单独的代理类,可以动态生成代理类。
- 灵活性:可以在运行时根据需要添加或修改代理行为。
- AOP(面向切面编程):动态代理是实现AOP的核心技术,可以用于日志记录、事务管理、权限控制等。
在C++中,没有像Java或Python那样原生支持动态代理的机制。但是,我们可以通过一些高级的C++特性和设计模式来实现类似的功能。通常,C++中实现动态代理需要使用模板、继承和虚函数来模拟动态代理行为。
示例:使用模板和继承实现简单的动态代理
我们将实现一个动态代理,它在方法调用前后添加日志记录。
接口和具体类
首先,定义一个接口和一个具体类:
#include <iostream>
// 接口
class IService {
public:
virtual ~IService() = default;
virtual void perform() = 0;
};
// 具体类
class RealService : public IService {
public:
void perform() override {
std::cout << "Performing service..." << std::endl;
}
};
动态代理类
接着,创建一个动态代理类,使用模板来接受任意类型的目标对象:
#include <memory>
// 动态代理类
template <typename T>
class DynamicProxy : public T {
public:
template <typename... Args>
DynamicProxy(Args&&... args) : target(std::make_unique<T>(std::forward<Args>(args)...)) {}
void perform() override {
std::cout << "Before method perform" << std::endl;
target->perform();
std::cout << "After method perform" << std::endl;
}
private:
std::unique_ptr<T> target;
};
主函数测试
最后,在主函数中使用动态代理来访问服务对象:
int main() {
// 使用动态代理
DynamicProxy<RealService> proxyService;
proxyService.perform();
return 0;
}
运行结果
当运行上述代码时,输出将如下:
Before method perform
Performing service...
After method perform
解释
- IService:定义了一个接口,包含一个纯虚函数
perform
。 - RealService:实现了
IService
接口,提供了具体的服务实现。 - DynamicProxy:使用模板接受任意类型的目标对象,继承自目标对象的类型。在
perform
方法中,添加日志记录并调用目标对象的方法。 - main:创建一个
DynamicProxy<RealService>
对象,并调用perform
方法,观察输出结果。
扩展:使用函数指针和Lambda实现更多代理功能
如果需要代理多个方法,可以使用函数指针或Lambda表达式来实现更灵活的动态代理。
示例代码
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
// 接口
class IService {
public:
virtual ~IService() = default;
virtual void perform() = 0;
virtual void anotherMethod(const std::string& msg) = 0;
};
// 具体类
class RealService : public IService {
public:
void perform() override {
std::cout << "Performing service..." << std::endl;
}
void anotherMethod(const std::string& msg) override {
std::cout << "Another method: " << msg << std::endl;
}
};
// 动态代理类
class DynamicProxy : public IService {
public:
DynamicProxy(std::shared_ptr<IService> target) : target(target) {
// 代理 perform 方法
methods["perform"] = [this]() {
std::cout << "Before method perform" << std::endl;
target->perform();
std::cout << "After method perform" << std::endl;
};
// 代理 anotherMethod 方法
methods["anotherMethod"] = [this](const std::string& msg) {
std::cout << "Before method anotherMethod" << std::endl;
target->anotherMethod(msg);
std::cout << "After method anotherMethod" << std::endl;
};
}
void perform() override {
methods["perform"]();
}
void anotherMethod(const std::string& msg) override {
methods["anotherMethod"](msg);
}
private:
std::shared_ptr<IService> target;
std::unordered_map<std::string, std::function<void()>> methods;
std::unordered_map<std::string, std::function<void(const std::string&)>> methods;
};
int main() {
std::shared_ptr<IService> realService = std::make_shared<RealService>();
DynamicProxy proxyService(realService);
proxyService.perform();
proxyService.anotherMethod("Hello, Proxy!");
return 0;
}
解释
- IService:定义了包含多个方法的接口。
- RealService:实现了
IService
接口,提供具体的服务实现。 - DynamicProxy:使用
std::function
和std::unordered_map
来存储代理的方法,通过Lambda表达式实现方法的代理。在构造函数中初始化这些方法。 - main:创建一个
RealService
对象,并通过DynamicProxy
访问其方法,观察输出结果。
总结
尽管C++没有原生支持动态代理,但通过使用模板、继承、虚函数以及函数指针或Lambda表达式,可以实现类似于动态代理的功能。上述示例展示了如何在C++中实现动态代理,用于添加日志记录或其他功能。这种方法不仅灵活,而且可以在不修改目标对象的情况下,动态地增强其功能。
动态代理是一种强大的技术,可以在运行时动态生成代理对象,添加灵活的行为控制。无论是在Java还是Python中,动态代理都能够减少冗余代码,提高系统的灵活性和可扩展性,特别适用于需要在运行时进行行为增强的场景,如日志记录、事务管理和权限控制。
模式对比
代理模式:主要用于控制对对象的访问,可以在访问前后添加额外的功能。
装饰模式:主要用于动态地为对象添加新的功能,可以叠加多个装饰器。
命令模式:主要用于将请求封装为对象,使得请求可以被参数化、排队、记录日志以及支持撤销操作。
每种模式都有其特定的用途和实现方式,选择使用哪种模式取决于具体的需求和场景。