目录
代理模式(Proxy Pattern)是一种【结构型】设计模式,它允许通过代理对象控制对另一个对象(目标对象)的访问。代理对象在客户端和目标对象之间充当中间层,负责处理与目标对象的交互,例如延迟加载、访问控制、缓存或日志记录等。
一、模式核心概念与结构
代理模式包含三个核心角色:
- 抽象主题(Subject):定义目标对象和代理对象的共同接口,客户端通过该接口与目标对象交互。
- 目标对象(Real Subject):实现抽象主题接口,提供实际的业务逻辑。
- 代理对象(Proxy):实现抽象主题接口,持有目标对象的引用,并在调用目标对象方法前后执行额外操作。
二、C++ 实现示例:图像加载代理
以下是一个经典的代理模式示例,演示如何使用代理模式实现图像的延迟加载:
#include <iostream>
#include <string>
#include <memory>
// 抽象主题:图像
class Image {
public:
virtual ~Image() = default;
virtual void display() const = 0;
};
// 目标对象:真实图像
class RealImage : public Image {
private:
std::string filename;
public:
RealImage(const std::string& fname) : filename(fname) {
loadFromDisk();
}
void display() const override {
std::cout << "Displaying image: " << filename << std::endl;
}
// 模拟从磁盘加载图像(耗时操作)
void loadFromDisk() const {
std::cout << "Loading image: " << filename << std::endl;
}
};
// 代理对象:图像代理
class ProxyImage : public Image {
private:
std::string filename;
mutable std::shared_ptr<RealImage> realImage; // 延迟加载
public:
ProxyImage(const std::string& fname) : filename(fname), realImage(nullptr) {}
void display() const override {
if (!realImage) {
// 延迟加载:首次调用时才创建真实对象
realImage = std::make_shared<RealImage>(filename);
}
realImage->display();
}
};
// 客户端代码
int main() {
// 使用代理对象加载图像
Image* image = new ProxyImage("test.jpg");
// 第一次调用:加载并显示
std::cout << "First display:" << std::endl;
image->display();
std::cout << "\n";
// 第二次调用:直接显示(无需重新加载)
std::cout << "Second display:" << std::endl;
image->display();
delete image;
return 0;
}
三、代理模式的关键特性
- 接口一致性:
- 代理对象和目标对象实现相同的接口,客户端无需区分使用的是代理还是真实对象。
- 控制访问:
- 代理可以在访问目标对象前后添加额外逻辑,如权限检查、缓存、延迟加载等。
- 解耦客户端与目标对象:
- 客户端通过代理间接访问目标对象,降低了系统的耦合度。
四、代理模式的常见类型
- 远程代理(Remote Proxy):
- 为远程对象提供本地代理,隐藏网络通信细节。
- 例如:RPC(远程过程调用)、Web 服务客户端。
- 虚拟代理(Virtual Proxy):
- 延迟创建开销大的对象,在需要时才初始化。
- 例如:图像延迟加载、大型文件的按需读取。
- 保护代理(Protection Proxy):
- 控制对敏感对象的访问,进行权限检查。
- 例如:用户权限验证、防火墙代理。
- 智能引用代理(Smart Reference Proxy):
- 在访问对象时添加额外操作,如引用计数、锁定。
- 例如:智能指针、数据库连接池。
- 缓存代理(Caching Proxy):
- 缓存目标对象的结果,提高性能。
- 例如:HTTP 缓存代理、数据库查询缓存。
五、应用场景
- 延迟加载:
- 当创建对象成本高时,如加载大型图像或初始化复杂资源。
- 访问控制:
- 限制对某些对象的访问,如基于用户权限的资源访问。
- 日志记录:
- 在方法调用前后添加日志,记录操作细节。
- 资源池管理:
- 管理共享资源,如数据库连接、线程池。
- 远程通信:
- 隐藏网络通信细节,提供本地接口访问远程服务。
六、代理模式与其他设计模式的关系
- 装饰模式:
- 代理模式控制对象访问,装饰模式增强对象功能。
- 代理模式的重点是访问控制,装饰模式的重点是功能扩展。
- 适配器模式:
- 适配器模式改变对象接口,代理模式保持接口一致。
- 适配器模式用于不兼容接口的转换,代理模式用于控制访问。
- 外观模式:
- 代理模式代表单个对象,外观模式代表子系统。
- 代理模式的粒度更细,外观模式的粒度更粗。
七、C++ 标准库中的代理模式应用
- 智能指针:
std::shared_ptr
和std::unique_ptr
是典型的代理模式实现,控制对资源的访问和生命周期。
- 迭代器:
- STL 迭代器作为容器元素的代理,隐藏底层数据结构的访问细节。
- 流缓冲区:
std::streambuf
作为实际 IO 设备的代理,提供缓冲功能。
- 函数对象(Functor):
- 函数对象可以看作是函数的代理,封装额外行为(如状态)。
八、优缺点分析
优点:
- 降低耦合:客户端与目标对象解耦,提高系统可维护性。
- 增强灵活性:可以在不修改目标对象的情况下添加新功能。
- 优化性能:通过延迟加载、缓存等机制提升系统效率。
缺点:
- 增加复杂度:引入代理对象可能使系统设计更复杂。
- 性能开销:代理层可能带来额外的调用开销。
- 调试困难:多层代理可能导致调试和理解代码变得困难。
九、实战案例:数据库连接代理
以下是一个数据库连接代理的实现示例,用于控制数据库连接的生命周期和权限:
#include <iostream>
#include <string>
#include <memory>
#include <mutex>
// 抽象主题:数据库连接
class DBConnection {
public:
virtual ~DBConnection() = default;
virtual void connect() = 0;
virtual void executeQuery(const std::string& query) = 0;
virtual void disconnect() = 0;
};
// 目标对象:真实数据库连接
class RealDBConnection : public DBConnection {
private:
std::string url;
std::string username;
std::string password;
bool isConnected;
public:
RealDBConnection(const std::string& u, const std::string& user, const std::string& pwd)
: url(u), username(user), password(pwd), isConnected(false) {}
void connect() override {
std::cout << "Connecting to database: " << url
<< " as user: " << username << std::endl;
// 实际连接逻辑...
isConnected = true;
}
void executeQuery(const std::string& query) override {
if (!isConnected) {
std::cout << "Error: Not connected to database!" << std::endl;
return;
}
std::cout << "Executing query: " << query << std::endl;
// 实际查询逻辑...
}
void disconnect() override {
if (isConnected) {
std::cout << "Disconnecting from database: " << url << std::endl;
// 实际断开连接逻辑...
isConnected = false;
}
}
};
// 代理对象:数据库连接代理(保护代理)
class DBConnectionProxy : public DBConnection {
private:
std::shared_ptr<DBConnection> realConnection;
std::string username;
std::string password;
bool hasAdminPrivileges;
mutable std::mutex mutex;
public:
DBConnectionProxy(const std::string& url, const std::string& user, const std::string& pwd)
: username(user), password(pwd), hasAdminPrivileges(false) {
// 验证用户权限
authenticate();
// 延迟创建真实连接
realConnection = nullptr;
}
void connect() override {
std::lock_guard<std::mutex> lock(mutex);
if (!realConnection) {
realConnection = std::make_shared<RealDBConnection>(
"jdbc:mysql://localhost:3306/mydb", username, password);
}
realConnection->connect();
}
void executeQuery(const std::string& query) override {
// 权限检查
if (!hasAdminPrivileges && isSensitiveQuery(query)) {
std::cout << "Error: Insufficient privileges to execute this query!" << std::endl;
return;
}
if (!realConnection) {
connect(); // 延迟连接
}
realConnection->executeQuery(query);
}
void disconnect() override {
if (realConnection) {
realConnection->disconnect();
}
}
private:
// 验证用户权限
void authenticate() {
// 模拟权限验证逻辑
if (username == "admin" && password == "admin123") {
hasAdminPrivileges = true;
std::cout << "User has admin privileges" << std::endl;
} else {
std::cout << "User has normal privileges" << std::endl;
}
}
// 检查是否为敏感查询
bool isSensitiveQuery(const std::string& query) const {
// 检测敏感操作(如DROP, DELETE等)
return (query.find("DROP") != std::string::npos ||
query.find("DELETE") != std::string::npos ||
query.find("ALTER") != std::string::npos);
}
};
// 客户端代码
int main() {
// 创建普通用户连接
DBConnection* normalUserConn = new DBConnectionProxy(
"jdbc:mysql://localhost:3306/mydb", "user", "pass");
// 执行普通查询
normalUserConn->executeQuery("SELECT * FROM users");
// 尝试执行敏感查询(会被拒绝)
normalUserConn->executeQuery("DROP TABLE users");
// 创建管理员连接
DBConnection* adminConn = new DBConnectionProxy(
"jdbc:mysql://localhost:3306/mydb", "admin", "admin123");
// 执行敏感查询(管理员允许)
adminConn->executeQuery("DROP TABLE temp_data");
delete normalUserConn;
delete adminConn;
return 0;
}
十、实现注意事项
- 接口一致性:
- 确保代理对象和目标对象实现相同的接口,避免客户端代码修改。
- 内存管理:
- 使用智能指针管理目标对象的生命周期,避免内存泄漏。
- 线程安全:
- 在多线程环境中,对共享资源的访问需进行同步(如使用互斥锁)。
- 代理链:
- 可以创建多层代理,每个代理负责不同的功能(如日志、缓存、权限)。
代理模式是 C++ 中实现访问控制和增强功能的重要工具,通过引入代理对象,可以在不修改目标对象的前提下,灵活地添加额外功能,提高系统的可扩展性和安全性。