公号:码农充电站pro
主页:https://codeshellme.github.iohtml
本篇来介绍代理模式(Proxy Design Pattern),经过代理模式能够控制和管理对象的访问。java
代理模式为对象提供一个代理,来控制对该对象的访问。代理表明了原始对象,而不是真实的对象自己。git
代理模式在不改变原始类代码的状况下,经过引入代理类来给原始类附加功能。github
代理模式的类图以下:算法
RealSubject 是原始类,Proxy 代理类,它们都实现了 Subject 接口,这使得代理类能够取代原始类。shell
原始类一般在代理类的后面,客户端不会直接访问原始类,而是只访问代理类。代理类在收到客户端的请求以后,会替客户端去访问原始类,而后将结果返回给客户端。缓存
代理类中保存了原始类对象的引用,代理类的实际操做仍是经过调用原始类来完成,但代理类除了完成原始类的基本功能以外,还能够添加一些其它必要的功能。网络
这看起来,代理模式很像装饰器模式,但它们的设计意图是不同的。代理模式的目的是为了控制和管理原始对象的访问;而装饰器模式是为了加强原有对象的能力,并且每每一个对象会被一个或多个装饰器屡次包装。架构
下面经过一个简单的例子,来看下如何使用代理模式。this
假如咱们如今有一个类 Server:
class Server { public void handleRequest() { // 处理过程 System.out.println("handle client request."); } }
Server 类中有一个 handleRequest
方法,用于处理客户端请求。
如今咱们想统计处理客户端请求须要的时间,最简单直接的作法是在 handleRequest
方法中添加计算时间的代码,以下:
public void handleRequest() { long startTime = System.currentTimeMillis(); // 处理过程 System.out.println("handle client request."); long endTime = System.currentTimeMillis(); long reqTime = endTime - startTime; System.out.println(reqTime); }
这样作的缺点是,在正常的业务处理流程中,添加了一些无关代码,使得统计代码与业务代码耦合在一块儿。
这种状况,就可使用代理模式来处理,而无需改动原有代码。
首选建立一个接口 ServerInterface,让代理类和原始类都继承该接口。
interface ServerInterface { void handleRequest(); }
原始类实现 ServerInterface 接口,Server 只需专一业务处理:
class Server implements ServerInterface { public void handleRequest() { // 处理过程 System.out.println("handle client request."); } }
下面建立代理类,负责统计时间:
class ServerProxy implements ServerInterface { private Server server; public ServerProxy(Server server) { this.server = server; } public void handleRequest() { long startTime = System.currentTimeMillis(); // 调用原始类 server.handleRequest(); long endTime = System.currentTimeMillis(); long reqTime = endTime - startTime; System.out.println(reqTime); } }
ServerProxy 中保存了 Server 对象的引用,会在必要的时候调用原始类,从而完成原始类的功能。
以后,客户端只须要与代理交互,而不须要跟原始类交互。
能够看到,这种实现方式并无直接修改 Server,而是建立了 Server 的代理,避免给 Server 带来没必要要的麻烦。
使用继承的方式来实现代理
上面这种代理的实现方式,使用的是组合的方式,也就是代理类中保存了一个原始类对象的引用。
若是在实际的项目中,原始类来自第三方库,这样就不能让原始类实现一个接口,由于咱们不能修改第三方库。
此时,为了使用代理模式,能够继承的方式,也就是让代理类继承原始类,以下:
class ServerProxy extends Server { public void handleRequest() { long startTime = System.currentTimeMillis(); // 调用原始类 super.handleRequest(); long endTime = System.currentTimeMillis(); long reqTime = endTime - startTime; System.out.println(reqTime); } }
此时,代理模式的类图就变成了下面这样:
代理模式有不少的应用场景,下面来看一些经常使用的。
远程代理
远程代理用于控制访问远程对象,客户端与原始类在不一样的地址空间中,远程代理经过网络来为客户端提供服务。
远程代理的架构图以下:
上图中的客户辅助对象就是代理,客户端将请求发给代理,代理将请求经过网络转发给服务辅助对象。
服务辅助对象接收代理的请求后,再将请求转给真实的服务对象,服务对象处理完请求后,再将处理结果一步步的传给客户端。
在 Java 中能够经过 RMI 来构建远程代理服务。
虚拟代理
虚拟代理用于控制访问建立开销大的资源,它做为建立开销大的对象的表明。
在虚拟代理中,只有咱们真正须要一个对象的时候,才会建立它。
在对象建立完成以前,由虚拟代理来处理客户端的访问;在对象建立以后,虚拟代理将客户端的请求委托给真实对象处理。
保护代理
保护代理基于访问权限来控制对资源的访问,保护代理能够不让客户端访问某些资源。
防火墙代理
防火墙代理用于控制网络资源的访问,使资源免于“坏客户”的攻击。
智能引用代理
当资源被引用时,计算资源被引用的次数。
缓存代理
为开销大的资源提供暂存服务,以减小计算和网络延迟。
代理模式有一个缺点,就是须要在代理类中实现原始类的全部方法,并且若是原始类不少的话,就须要建立不少的代理类,从而致使项目中类的数量倍增。若是代理类的功能相近的话,还会致使许多重复代码。
为了解决这个问题,可使用动态代理。动态代理不须要事先为每一个原始类编写代理类,而是在运行时,动态的为原始类建立代理类,而后用代理类来替换原始类。
在 Java 中可使用 java.lang.reflect
包中的 Proxy 类和 InvocationHandler 接口来实现动态代理,其中用到了Java 反射的原理。
代理模式提供了一个原有服务的代理,这样使得客户不直接访问原有服务,而是经过代理间接访问原服务,达到了控制原有服务访问的目的。
代理模式能够用组合与继承两种方式来实现。代理模式有时候会致使代理类过多和代码重复的问题,这个问题能够用动态代理来解决。
有时候代理模式看起来像是装饰者模式,但它们的设计意图是不同的。
代理模式的应用场景有不少,好比远程代理,虚拟代理,保护代理等。
(本节完。)
推荐阅读:
欢迎关注做者公众号,获取更多技术干货。