突破编程_C++_设计模式(代理模式)

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)透明性问题: 代理模式可能会隐藏原始对象的真实行为和状态,使得调试和排查问题变得更加困难。因此,在使用代理模式时,需要确保代理对象的行为与原始对象保持一致,并提供足够的调试信息。

相关推荐
qmx_073 分钟前
MFC -文件类控件
c++·mfc
Sunsets_Red44 分钟前
Linux 系统
linux·运维·服务器·c++·学习·系统架构·系统安全
程序猿练习生1 小时前
C++速通LeetCode中等第18题-删除链表的倒数第N个结点(最简单含注释)
c++·leetcode·链表
丶白泽1 小时前
重修设计模式-结构型-适配器模式
前端·设计模式·适配器模式
CV猿码人1 小时前
设计模式-适配器模式
java·设计模式·适配器模式
极地星光1 小时前
设计模式-适配器模式
c++·设计模式·适配器模式
mljy.1 小时前
STL简介
c++·学习
职创未来官方1 小时前
大话C++:第11篇 类的定义与封装
c++·面向对象·封装··嵌入式物联网·访问修饰符
田小呱1 小时前
C/C++事件驱动的业务框架
c语言·c++
蠢蠢的打码1 小时前
8587 行编辑程序
数据结构·c++·算法·链表