一、动机
在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦
如何在不是去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层简介层时软件开发中常见的解决方式。
为什么使用代理模式?
第一个,客户端有时无法直接操作某些对象。 比如,在分布式应用中,你需要调用的对象可能是运行在另外一台服务器上的,当你访问它时,就必须要通过网络才能访问。如果你让客户端直接去调用,那么就意味着客户端需要处理网络服务,包括连接、打包、传包、解包等复杂操作;而这时如果你使用代理模式,在客户端和远程服务端之间建立一个网络代理对象,那么客户端只需要调用代理对象就能跟远程对象建立联系,甚至就像调用本地对象一样。这其实就是我们常说的 RPC 服务的基本原理,本质上就是代理模式。
第二个,客户端执行某些耗时操作容易造成服务端阻塞。 比如,在类似有道、石墨、语雀这样的云编辑器里进行文案编写时,拷贝多张图片可能就是一件非常耗时的操作,使用者并不希望在执行拷贝图片的操作后,打字就无法正常操作甚至无法查看其他页面。这时,对于软件设计者来说,图片的加载就可以通过代理模式来解决:标示图片所在位置,然后使用代理对象去读取图片资源,这样就不会影响其他客户端与服务端之间的操作了。
第三个,服务端需要控制客户端的访问权限。 代理模式除了前面提到的扩展功能外,另一个更为重要的功能是做权限控制。比如,某一项业务由于安全原因只能让一部分特定的用户去访问,如果在原有功能的基础上再增加权限过滤功能就会增加代码的耦合性,并且也不方便组件的复用。其实,这时做一个代理类就可以解决该问题,对于特定的接口来说,只需要指定所有请求必须通过该代理类,然后由该代理类做权限判断即可。
二、模式定义
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
代理模式的原始定义是:让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。
代理模式是作为对象之间的一种中间结构来使用的,通过构建一个代理对象来对原始的功能进行委托处理,其中有一个很重要的功能就是控制对象的访问。
代理模式和装饰模式很类似,都是在不改变同一个接口功能的前提下,对原有接口功能做扩展。但是代理模式的应用却比装饰模式更为广泛,因为代理模式并不执着于链式结构,而是采用更为灵活的单一结构,在很多框架和组件的设计里都能看到代理模式的身影,比如,JDK 的动态代理机制、Spring 的 AOP 机制、Dubbo 框架等。
三、结构
四、要点总结
“增加一层简介层“ 时软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象对带来很多问题,作为间接层的 proxy 对象便是解决这一问题的常用手段。
具体 proxy 设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如 copy-on-write 技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做 proxy。
Proxy 并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损失一些透明性是可以接受的。
五、代码示例
5.1 使用代理前
class ISubject{
public:
virtual void process();
};
class RealSubject: public ISubject{
public:
virtual void process(){
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new RealSubject();
}
void DoTask(){
//...
subject->process();
//....
}
};
5.2 使用代理后
class ISubject{
public:
virtual void process();
};
//Proxy的设计
class SubjectProxy: public ISubject{
public:
virtual void process(){
//对RealSubject的一种间接访问
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new SubjectProxy();
}
void DoTask(){
//...
subject->process();
//....
}
};
六、使用场景
第一类,虚拟代理,适用于延迟初始化,用小对象表示大对象的场景。这个“大对象”会包含大量 IO 资源,比如图片、大文件、模型文件等。我们都知道,大对象通常很占用内存空间,一直保持其运行会很消耗系统资源,这时就可以使用代理模式。那怎么来做呢?可以先创建一个消耗相对较小的对象来代理这个大对象的创建,而实际上真实的大对象只会在真正需要时才会被创建,这样的代理方式就被称为虚拟代理。比如,在 Java 中的 CopyOnWriteArrayList 数组对象的实现就是使用了虚拟代理的方式,目的就是要让操作延迟,只有对象被真正用到的时候才会被克隆。
第二类,保护代理,适用于服务端对客户端的访问控制场景。代理模式有一个非常重要的应用场景就是控制一个对象对另一个对象的访问与使用权限。当客户端通过代理对象访问服务端的原始对象时,代理对象会根据具体的规则来判断客户端是否有访问权限。比如,防火墙其实就是一种保护代理的具体实践。
第三类,远程代理,适用于需要本地执行远程服务代码的场景。 在这种场景中,代理对象会隐藏处理所有与网络相关的复杂细节。随着微服务架构的流行,越来越多的程序应用部署在多台服务器上,各自服务都更专注于各自的业务,当需要使用其他服务时就会频繁进行远程服务调用,但不可能所有的业务都要自己实现网络调用,于是就出现了的远程代理框架,比如,gRpc、Dubbo等。
第四类,日志记录代理,适用于需要保存请求对象历史记录的场景,比如,日志监控。客户端在调用请求时,并不会感知到日志记录,这是因为代理对象在原始对象周围添加了监控功能。
第五类,缓存代理,适用于缓存客户请求结果并对缓存生命周期进行管理的场景。比如,商品详情页通常包含大量图片和文字介绍,代理对象可以对重复请求相同的结果进行缓存。