1 代理模式的基本概念
在C++中,代理模式是一种常见的设计模式,用于在对象之间提供一种间接层或"代理",以控制对实际对象的访问。代理模式的主要目的是在不改变原始对象接口的情况下,增加额外的逻辑或控制。比如常见的远程代理:当对象在不同的地址空间(如不同的计算机或网络位置)时,代理可以提供一个本地代表,隐藏对象存在于不同地址空间的事实。
代理模式包含以下主要组件:
(1)Subject(主题): 定义了实际对象(Real Object)和代理对象(Proxy Object)共同实现的接口。这个接口定义了实际对象的行为。
(2)Real Object(实际对象): 实现了 Subject 接口,定义了实际的功能。
(3)Proxy Object(代理对象): 同样实现了 Subject 接口,但它并不直接实现功能。相反,它持有对 Real Object 的引用,并在需要时代表 Real Object 执行操作。代理对象可以在调用 Real Object 的方法之前或之后添加额外的逻辑,如权限检查、日志记录、缓存等。
2 代理模式的实现步骤
在 C++ 中实现代理模式通常涉及以下步骤:
(1)定义接口
首先,需要定义一个接口(或者抽象类),该接口定义了代理类和被代理类必须实现的共同方法。这个接口定义了代理模式的公共契约,确保客户端代码能够统一地与代理类和被代理类交互。
(2)创建被代理类
接下来,需要创建被代理类(Real Subject),这个类实现了在第一步中定义的接口。被代理类是实际执行功能的类,它包含了客户端需要的实际业务逻辑。
(3)创建代理类
然后,需要创建代理类(Proxy)。代理类同样实现了第一步中定义的接口。在代理类的方法中,可以添加额外的逻辑或控制。这些额外的逻辑可以是在调用被代理类方法之前、之后,或者替代被代理类方法的逻辑。
(4)实现代理逻辑
在代理类中,需要持有对被代理类的引用或指针。当客户端调用代理类的方法时,代理类可以根据需要决定是直接执行被代理类的方法,还是在执行前后添加额外的逻辑。
(5)使用代理
最后,客户端代码通过代理类与被代理类交互。客户端不需要知道代理类的存在,它只需要关心接口定义的方法。代理类负责在客户端和被代理类之间传递请求,并在必要时添加额外的逻辑或控制。
下面是一个简单的代理模式实现示例:
首先是接口定义:
cpp
#include <iostream>
#include <memory>
class ISubject {
public:
virtual ~ISubject() = default;
virtual void Request() = 0; // 纯虚函数,要求实现类提供具体实现
};
接着是被代理类:
cpp
class RealSubject : public ISubject {
public:
void Request() override {
// 实现具体的业务逻辑
std::cout << "RealSubject::Request() is called." << std::endl;
}
};
然后是代理类:
cpp
class ProxySubject : public ISubject
{
public:
ProxySubject() : realSubject(std::make_shared<RealSubject>()) {}
~ProxySubject() { ; }
void Request() override {
// 在调用被代理类的方法前后添加额外的逻辑
std::cout << "ProxySubject::BeforeRequest()" << std::endl;
realSubject->Request();
std::cout << "ProxySubject::AfterRequest()" << std::endl;
}
private:
std::shared_ptr<RealSubject> realSubject; // 持有对被代理类的引用
};
最后是客户端代码:
cpp
int main()
{
std::shared_ptr<ISubject> subject = std::make_shared<ProxySubject>(); // 客户端通过代理类与被代理类交互
subject->Request(); // 调用代理类的方法
return 0;
}
上面代码的输出为:
ProxySubject::BeforeRequest()
RealSubject::Request() is called.
ProxySubject::AfterRequest()
在上面代码中,ISubject 是一个接口,定义了 Request 方法。RealSubject 是实现了 ISubject 接口的被代理类,它提供了 Request 方法的具体实现。ProxySubject 是代理类,它同样实现了 ISubject 接口,并在其 Request 方法中添加了额外的逻辑,如打印一些信息。
客户端代码通过 ProxySubject 类型的指针调用 Request 方法,代理类在调用被代理类的方法前后输出了额外的信息。
3 代理模式的应用场景
C++ 代理模式的应用场景主要涉及以下几个方面:
(1)远程代理: 当一个对象位于不同的地址空间时,代理模式可以提供一个局域代表对象。这在分布式系统中尤其有用,例如,一个客户端可能需要通过代理访问位于远程服务器上的对象。
(2)虚拟代理: 当创建一个资源消耗很大或者创建过程复杂的对象时,可以使用虚拟代理来延迟对象的创建。虚拟代理在真实对象创建成功之前扮演其替身,并在用户请求时转发给真实对象。例如,在网页加载过程中,未打开的图片框通过虚拟代理替代真实的图片,代理存储了真实图片的路径和尺寸。
(3)安全代理: 安全代理用于控制对真实对象的访问权限。它可以实现不同用户的访问权限控制,为不同用户提供不同的操作函数。
(4)智能引用: 智能引用代理可以在调用真实对象的方法时,处理额外的逻辑。例如,代理可以在调用前后添加日志记录、性能监控等。
(5)外部接口代理: 当使用外部提供的接口(如 STL 或动态库 API 函数)时,由于这些接口可能会发生改变,直接使用可能导致项目与接口函数之间的耦合度很高。通过使用代理模式,可以在项目中用一个类封装这个外部接口,将耦合控制在这个类中,并管理外部接口函数的所有功能。这样,当外部接口函数有变化时,只需要修改代理类中的耦合部分,而无需在整个项目中寻找和修改。
以上这些应用场景都体现了代理模式的主要优点,即提供一种代理以控制对某个对象的访问,减少系统耦合度,增强系统的灵活性和可扩展性。
3.1 代理模式应用于远程代理
代理模式应用于远程代理的实现中,通常会创建一个本地代理对象,它代表一个远程对象。这个本地代理对象可以处理与远程对象的通信,包括序列化和反序列化数据、通过网络发送请求和接收响应等。下面是一个简单的 C++ 示例,展示了如何使用代理模式实现远程代理。
首先,定义远程接口 IRemoteObject:
cpp
#include <iostream>
#include <memory>
#include <string>
class IRemoteObject {
public:
virtual ~IRemoteObject() = default;
virtual void PerformAction() = 0; // 远程对象需要执行的操作
};
然后,实现远程对象 RemoteObject,它位于远程地址空间:
cpp
class RemoteObject : public IRemoteObject {
public:
void PerformAction() override {
// 远程对象执行的实际操作
std::cout << "RemoteObject::PerformAction() is called." << std::endl;
}
};
接下来,创建本地代理 LocalProxy,它实现了相同的接口并处理与远程对象的通信:
cpp
// 假设有一个用于远程通信的类
class RemoteCommunication {
public:
void SendRequestToRemote(const std::string& action) {
// 在这里实现与远程对象的通信,例如通过网络发送请求
std::cout << "Sending request to remote: " << action << std::endl;
// 假设远程对象执行了请求并返回了结果
}
};
class LocalProxy : public IRemoteObject
{
public:
LocalProxy(std::shared_ptr<RemoteCommunication> comm) : communication(communication) {}
void PerformAction() override {
// 在调用远程对象之前,可以添加本地逻辑
std::cout << "LocalProxy::BeforeRemoteCall()" << std::endl;
// 通过通信层发送请求到远程对象
communication->SendRequestToRemote("PerformAction");
// 在调用远程对象之后,可以添加本地逻辑
std::cout << "LocalProxy::AfterRemoteCall()" << std::endl;
}
private:
std::shared_ptr<RemoteCommunication> communication;
};
最后,客户端代码通过本地代理与远程对象交互:
cpp
int main()
{
// 创建本地代理实例,并将远程通信实例传递给它
std::shared_ptr<LocalProxy> proxy = std::make_shared<LocalProxy>(std::make_shared<RemoteCommunication>());
// 客户端通过代理调用方法
proxy->PerformAction();
return 0;
}
上面代码的输出为:
LocalProxy::BeforeRemoteCall()
Sending request to remote: PerformAction
LocalProxy::AfterRemoteCall()
在这个示例中,LocalProxy 类充当了远程对象 RemoteObject 的代理。当客户端调用 proxy->PerformAction() 时,LocalProxy 会处理与远程对象的通信,包括发送请求和接收响应。RemoteCommunication 类模拟了实际的远程通信过程,但在实际应用中,可能需要使用网络库(如 Boost.Asio、Poco 等)来实现这部分逻辑。
这个示例简化了实际的远程过程调用(RPC)机制。在真实的业务应用中,远程代理的实现可能会涉及更多的细节,如序列化/反序列化数据、异常处理、连接管理等。
3.2 代理模式应用于安全代理
如果需要想控制对某个对象的访问权限时,可以创建一个代理对象来检查访问权限,并根据这些权限来允许或拒绝访问。下面是一个简单的示例,其中 SecuredObject 是一个需要保护的对象,而 SecurityProxy 是它的安全代理。
首先,定义 ISecuredObject 接口:
cpp
#include <iostream>
#include <memory>
#include <string>
class ISecuredObject {
public:
virtual ~ISecuredObject() = default;
virtual void SensitiveOperation() = 0; // 敏感操作
};
然后,实现 SecuredObject 类:
cpp
class SecuredObject : public ISecuredObject {
public:
void SensitiveOperation() override {
// 执行敏感操作
std::cout << "SecuredObject::SensitiveOperation() is called." << std::endl;
}
};
接下来,创建 SecurityProxy 类作为安全代理:
cpp
class SecurityProxy : public ISecuredObject
{
public:
SecurityProxy(std::shared_ptr<ISecuredObject> obj, const std::string& user)
: securedObject(obj), allowedUser(user) {}
void SensitiveOperation() override {
std::string currentUser = "currentUser"; // 假设这是当前用户
if (currentUser == allowedUser) {
// 如果当前用户是允许的用户,则执行操作
securedObject->SensitiveOperation();
}
else {
// 否则,拒绝访问
std::cout << "Access denied for user: " << currentUser << std::endl;
}
}
private:
std::shared_ptr<ISecuredObject> securedObject;
std::string allowedUser;
};
最后,客户端代码通过安全代理与 SecuredObject 交互:
cpp
int main()
{
// 创建安全代理实例,并指定允许的用户
SecurityProxy proxy(std::make_shared<SecuredObject>(), "allowedUser");
// 假设当前用户是允许的用户
std::string currentUser = "allowedUser";
if (currentUser == "allowedUser") {
// 通过代理调用敏感操作,允许访问
proxy.SensitiveOperation();
}
else {
// 如果当前用户不是允许的用户,则不调用代理的SensitiveOperation方法
std::cout << "Not allowed to call SensitiveOperation for user: " << currentUser << std::endl;
}
return 0;
}
上面代码的输出为:
Access denied for user: currentUser
在这个示例中, SecurityProxy 类封装了 SecuredObject 对象,并检查当前用户是否有权限执行 SensitiveOperation 方法。如果当前用户是允许的用户(在此示例中硬编码为 "allowedUser" ),则代理将调用实际对象的 SensitiveOperation 方法。否则,它将拒绝访问并输出一个拒绝消息。
4 代理模式的优点与缺点
C++ 代理模式的优点主要包括:
(1)控制访问: 代理模式提供了一种控制对原始对象访问的机制。通过代理,你可以增加访问前的安全检查、日志记录或权限验证等。
(2)功能增强: 代理可以在调用原始对象的方法前后添加额外的逻辑,比如缓存、延迟加载或性能统计等。
(3)远程访问: 对于远程对象,代理可以隐藏远程调用的细节,如网络延迟、异常处理等,使得本地代码调用远程对象就像调用本地对象一样简单。
(4)接口统一: 代理可以使得不同类的对象使用统一的接口,从而简化客户端代码。
(5)解耦: 代理模式有助于减少系统与真实对象之间的耦合度,使得系统更加灵活和可维护。
然而,C++ 代理模式也存在一些缺点:
(1)性能开销: 由于代理模式需要额外创建一个代理对象来转发请求,这可能会引入一定的性能开销。虽然这种开销通常很小,但在高性能要求的场景下可能需要注意。
(2)复杂性增加: 使用代理模式可能会增加代码的复杂性,因为需要创建和管理代理对象。这可能会使得代码更难理解和维护。
(3)滥用风险: 如果过度使用代理模式,可能会导致代码结构变得过于复杂和混乱。在设计系统时,需要权衡是否真正需要代理模式,并避免不必要的复杂性。
(4)透明性问题: 代理模式可能会隐藏原始对象的真实行为和状态,使得调试和排查问题变得更加困难。因此,在使用代理模式时,需要确保代理对象的行为与原始对象保持一致,并提供足够的调试信息。