设计模式16-代理模式
动机
- 在面向对象系统中有一些对象由于某种原因比如对象创建的开销很大或者某些操作需要安全控制,或者需要进程外的访问等情况。直接访问会给使用者或者系统结构带来许多麻烦。
- 那么如何在不失去透明操作对象的同时来管理和控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。
- 代理模式的主要动机是为某个对象提供一个代理或占位符,以便控制对该对象的访问。这种模式可以在不改变客户端代码的前提下,为对象的访问提供额外的功能,比如延迟初始化、访问控制、日志记录等。
常见的代理模式的使用场景包括:
- 远程代理:为一个位于不同地址空间的对象提供本地代表。
- 虚拟代理:根据需要创建开销较大的对象。
- 保护代理:控制对原始对象的访问,可以对权限进行控制。
- 智能引用代理:当访问对象时,执行一些附加操作,比如计数、日志记录等。
定义与结构
模式定义
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
结构
代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在这个图例中,有几个关键组件和交互过程:
Client(客户端):客户端是发起请求的一方,它不知道真正的实体对象(Subject)是如何被实现和管理的。客户端通过代理(Proxy)来间接地与实体对象交互。
Subject(抽象主题/接口):这是一个定义了RealSubject和Proxy的共通接口的抽象类或接口。它声明了RealSubject和Proxy应该实现的方法,使得客户端能够通过相同的接口与它们交互。
RealSubject(真实主题):这是被代理的对象,它实现了Subject接口。RealSubject包含了执行实际业务逻辑的方法。
Proxy(代理):代理也是一个实现了Subject接口的对象。它持有对RealSubject的引用,并且可以控制对RealSubject的访问。代理可以在将请求传递给RealSubject之前或之后执行额外的操作,如权限检查、日志记录、事务管理等。
交互过程:
- 客户端向代理发送请求(Request())。
- 代理接收到请求后,可能会执行一些预处理操作(如权限检查)。
- 然后,代理将请求转发给RealSubject(如
realSubject->Request(0);
所示,尽管这里的表示方式略显简化)。 - RealSubject执行实际的业务逻辑并可能返回一个结果。
- 代理可能会接收到结果并进行后处理,然后将结果返回给客户端。
代理模式的主要优点包括:
- 减少直接依赖:客户端与RealSubject之间的直接依赖被解耦,提高了系统的灵活性和可维护性。
- 控制访问:代理可以决定客户端是否可以访问RealSubject,以及何时可以访问。
- 增加额外的操作:代理可以在请求处理前后执行额外的操作,如日志记录、安全控制等。
这种模式在软件开发中非常有用,特别是当需要控制对某个对象的访问、减少系统组件之间的耦合度或者添加额外的处理逻辑时。
代理模式通常包括以下角色:
- Subject(抽象主题):声明了RealSubject和Proxy的公共接口,这样代理就可以用在任何使用RealSubject的地方。
- RealSubject(真实主题):定义了代理所代表的真实对象。
- Proxy(代理):保存一个引用使得代理可以访问RealSubject。实现Subject接口,代理操作在调用RealSubject之前后可以执行相关操作。
代码推导
下面是一个简单的C++代码示例,展示了代理模式的实现:
Subject接口:
class Subject { public: virtual void request() = 0; virtual ~Subject() = default; };
RealSubject类:
#include <iostream> class RealSubject : public Subject { public: void request() override { std::cout << "RealSubject: Handling request." << std::endl; } };
Proxy类:
class Proxy : public Subject { private: RealSubject* realSubject; public: Proxy() : realSubject(nullptr) {} ~Proxy() { delete realSubject; } void request() override { if (realSubject == nullptr) { realSubject = new RealSubject(); } std::cout << "Proxy: Logging the request." << std::endl; realSubject->request(); } };
客户端代码:
int main() { Proxy proxy; proxy.request(); // 实际请求通过代理进行 return 0; }
特点
优点:
- 控制对象访问:代理模式可以在不改变原始对象的情况下,控制对它的访问。例如,远程代理可以处理网络通信,虚拟代理可以延迟对象的创建。
- 增强功能:代理模式允许在不修改客户端代码的情况下增强对象的功能。例如,保护代理可以增加权限控制,智能引用代理可以记录访问日志。
- 解耦合:客户端与实际的实现对象解耦,客户端可以通过代理访问不同的实现对象。
缺点:
- 性能开销:由于增加了代理层,会造成请求处理的额外开销,尤其是在频繁调用的情况下,可能会影响性能。
- 复杂性增加:引入代理模式会增加系统的复杂性,增加类的数量和系统的理解难度。
- 潜在问题:如果不恰当地使用代理模式,可能会导致系统的设计不合理,带来维护困难。
应用
- 远程代理:为一个位于不同地址空间的对象提供本地代表。例如,在分布式系统中,客户端可以通过远程代理访问远程的对象。
- 虚拟代理:根据需要创建开销较大的对象。例如,在图形系统中,虚拟代理可以用于按需加载图像。
- 保护代理:控制对原始对象的访问,可以对权限进行控制。例如,在安全系统中,保护代理可以用于控制对敏感对象的访问。
- 智能引用代理:当访问对象时,执行一些附加操作,比如计数、日志记录等。例如,智能引用代理可以用于在访问对象时记录日志。
总结
- 代理模式提供了一种通过代理对象控制对原始对象访问的机制,使得我们可以在不改变客户端代码的情况下,对对象的访问进行控制和增强。尽管引入代理会增加一定的性能开销和系统复杂性,但在适当的场景下使用代理模式,可以显著提高系统的灵活性和可维护性。
- 增加一层间接层。是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中直接使用某些对象会带来很多问题作为间接层的代理对象必须解决这一问题的常见手段。
- 具体的代理设计模式的实现方法,实现粒度,都相差很大。有些可能对单个对象的做细粒度的控制。比如拷贝。或者写技术。有些可能对组件模块提供抽象代理层在架构层次对对象做代理。
- 代理模式并不一定要求保持接口完整的一致性。只要能够实现间接控制,有时候损失一些透明性是可以接受的。
实例说明
下面是几个不同类型的代理模式的应用场景,并通过C++代码示例来说明每种场景的实现。
1. 远程代理
远程代理用于访问位于不同地址空间的对象。在这里,我们模拟一个简单的远程方法调用。
#include <iostream> // Subject interface class Subject { public: virtual void request() = 0; virtual ~Subject() = default; }; // RealSubject implementation, which might be on a remote server class RealSubject : public Subject { public: void request() override { std::cout << "RealSubject: Handling request." << std::endl; } }; // Proxy implementation that handles communication with the remote RealSubject class RemoteProxy : public Subject { private: RealSubject* realSubject; public: RemoteProxy() : realSubject(nullptr) {} ~RemoteProxy() { delete realSubject; } void request() override { if (realSubject == nullptr) { std::cout << "RemoteProxy: Creating RealSubject remotely." << std::endl; realSubject = new RealSubject(); } std::cout << "RemoteProxy: Forwarding request to RealSubject." << std::endl; realSubject->request(); } }; // Client code int main() { RemoteProxy proxy; proxy.request(); // 实际请求通过代理进行 return 0; }
2. 虚拟代理
虚拟代理用于按需创建开销较大的对象。在这里,我们模拟一个按需加载图像的例子。
#include <iostream> // Subject interface class Image { public: virtual void display() = 0; virtual ~Image() = default; }; // RealSubject implementation, which represents an actual image class RealImage : public Image { private: std::string filename; void loadFromDisk() { std::cout << "Loading " << filename << std::endl; } public: RealImage(const std::string& filename) : filename(filename) { loadFromDisk(); } void display() override { std::cout << "Displaying " << filename << std::endl; } }; // Proxy implementation that creates RealImage only when needed class ProxyImage : public Image { private: std::string filename; RealImage* realImage; public: ProxyImage(const std::string& filename) : filename(filename), realImage(nullptr) {} ~ProxyImage() { delete realImage; } void display() override { if (realImage == nullptr) { realImage = new RealImage(filename); } realImage->display(); } }; // Client code int main() { ProxyImage image("test.jpg"); image.display(); // Loading and displaying the image image.display(); // Displaying the already loaded image return 0; }
3. 保护代理
保护代理用于控制对原始对象的访问,可以对权限进行控制。这里我们模拟一个简单的权限控制例子。
#include <iostream> // Subject interface class Subject { public: virtual void request() = 0; virtual ~Subject() = default; }; // RealSubject implementation, which represents an actual service class RealSubject : public Subject { public: void request() override { std::cout << "RealSubject: Handling request." << std::endl; } }; // Proxy implementation that controls access to RealSubject class ProtectionProxy : public Subject { private: RealSubject* realSubject; bool accessGranted; public: ProtectionProxy(bool access) : realSubject(new RealSubject()), accessGranted(access) {} ~ProtectionProxy() { delete realSubject; } void request() override { if (accessGranted) { std::cout << "ProtectionProxy: Access granted." << std::endl; realSubject->request(); } else { std::cout << "ProtectionProxy: Access denied." << std::endl; } } }; // Client code int main() { ProtectionProxy proxyWithAccess(true); proxyWithAccess.request(); // Access granted, forwarding request to RealSubject ProtectionProxy proxyWithoutAccess(false); proxyWithoutAccess.request(); // Access denied, not forwarding request return 0; }
4. 智能引用代理
智能引用代理在访问对象时执行一些附加操作,如引用计数、日志记录等。这里我们模拟一个记录访问日志的例子。
#include <iostream> #include <memory> // Subject interface class Subject { public: virtual void request() = 0; virtual ~Subject() = default; }; // RealSubject implementation, which represents an actual service class RealSubject : public Subject { public: void request() override { std::cout << "RealSubject: Handling request." << std::endl; } }; // Proxy implementation that logs access to RealSubject class LoggingProxy : public Subject { private: std::shared_ptr<RealSubject> realSubject; public: LoggingProxy() : realSubject(std::make_shared<RealSubject>()) {} void request() override { std::cout << "LoggingProxy: Logging request." << std::endl; realSubject->request(); } }; // Client code int main() { LoggingProxy proxy; proxy.request(); // Logging the request and forwarding it to RealSubject return 0; }