代理模式是一种结构型设计模式,让开发者能够提供对象的替代品或其占位符。代理对象控制着对于原对象的访问,并允许在将请求提交给原对象前后进行一些处理。代理模式为原对象提供一种代理以控制对这个对象的访问,并由代理对象控制对原对象的引用。
Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy
controls access to the original object, allowing you to perform something either before or after the request gets
through to the original object.
结构设计
为使用代理对象控制对某个对象(Real Subject)的访问,可以创建一个代理(Proxy)并封装对源对象的访问。为保证代理和这个对象的接口一致,还需提取公共接口(Subject),这样在任何需要控制这个对象的访问的地方,都可使用代理实现。代理模式包含如下角色:
Subject,抽象对象,声明了对象接口。代理必须遵循该接口才能伪装成原对象。
Real Subject,真实对象,实现了对象接口。
Proxy,代理类,包含一个指向服务对象的引用成员变量。代理完成其任务(如延迟初始化、记录日志、访问控制和缓存等)后会将请求传递给服务对象。
代理模式类图表示如下:
伪代码实现
接下来将使用代码介绍下代理模式的实现。
// 1、抽象对象,对接口进行声明
public interface Subject {void operation();
}// 2、真实对象,实现了接口
public class RealSubject implements Subject {@Overridepublic void operation() {System.out.println("---------do some thing in a real subject instance---------");}
}// 3、代理类,包含一个指向代理对象的引用成员变量
public class Proxy implements Subject {private RealSubject realSubject = new RealSubject();@Overridepublic void operation() {preOperation();realSubject.operation();afterOperation();}public void preOperation() {System.out.println("pre operation in the proxy");}public void afterOperation() {System.out.println("after operation in the proxy");}
}// 4、客户端调用
public class ProxyClient {public void test(){// (1) 声明接口并实例化代理类Subject subjectProxy = new Proxy();// (2) 调用对象接口subjectProxy.operation();}
}
注意:
(1) 尽管可以提供一个公共接口供代理和对象使用,但是更多的情况是这个对象的实现和代理的实现是两个不同的人或部门开发。一种可能的情况是开发代理类的是客户端开发人员,而开发服务器端类的服务器端开发人员。所以代理和这个对象的公共接口可能并不会被创建。(无法完全做到面向接口编程)
(2) 如果Proxy不需要知道待控制访问的对象的类型,则可使用统一的接口处理代理,而不需要为每个待控制访问的对象创建Proxy。
适用场景
在以下情况下可以考虑使用代理模式:
(1) 远程代理。本地执行远程服务,适用于服务对象位于服务器(本地服务器或远程服务器)上的情形。在这种情形中,代理通过网络传递客户端请求,负责处理所有与网络相关的复杂细节。
(2) 虚拟代理。如果需要创建一个资源消耗较大的对象,一直保持该对象运行会消耗系统资源。可以先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。虚拟代理是一种延迟初始化实现,无需在程序启动时就创建该对象,可将对象的初始化延迟到真正有需要的时候。
(3) Copy-on-Write代理。它是虚拟代理的一种实现,把克隆对象的操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
(4) 保护代理。该代理控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。如果只希望特定用户使用服务对象,可考虑使用代理模式。
(5) 缓存代理。为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。
(6) 日志记录代理。当需要保存对于服务对象的请求历史记录时,代理可以在向服务传递请求前进行记录。
(7) 智能引用。当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来,在没有客户端使用某个重量级对象时立即销毁该对象等。
(8) 图片代理。当需要对大图浏览进行控制时,可以考虑使用代理模式。用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
(9) 动态代理。动态代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。在传统的代理模式中,客户端通过Proxy调用RealSubject类的request()方法,同时还在代理类中封装了其他方法(如preRequest()和postRequest()),可以处理一些其他问题。如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数,此外,如何在事先不知道真实主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问题。
优缺点
代理模式,在访问对象时引入了一定程度的间接性。代理模式有以下优点:
(1) 符合开闭原则。可以在不对服务或客户端做出修改的情况下创建新代理。
(2) 隐藏一个对象存在于不同地址空间的事实。如客户端调用服务器端方法,使用代理后,客户端像调用本地方法一样,调用服务器端方法。
(3) 允许在访问一个对象时,进行一些额外的处理。如将组合后的数据返回给调用者,延迟对象的创建时间,对对象进行生命周期管理等。
但是代理模式也存在以下缺点:
(1) 服务响应可能会延迟。由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
(2) 代码复杂度上升。实现代理模式需要额外的工作,有些代理模式的实现非常复杂(如动态代理)。
参考
《设计模式:可复用面向对象软件的基础》 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides 著 李英军, 马晓星 等译
https://www.runoob.com/design-pattern/proxy-pattern.html 代理模式
https://www.cnblogs.com/adamjwh/p/9102037.html 简说设计模式——代理模式
https://blog.csdn.net/ShuSheng0007/article/details/80864854 秒懂Java代理与动态代理模式
https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/proxy.html 代理模式
https://refactoringguru.cn/design-patterns/proxy 代理模式