意图
Proxy是结构模式的一种,它能够让你为另外一个对象提供一个替身或者占位符来控制对它的访问。
问题
为什么要控制对对象的访问?比如:你有一个需要消耗大量系统资源的对象。你时不时会用到它,但不是一直使用。
因此,这个对象不用再程序启动时创建,而是当真正需要它的时候再创建。每个用到这个对象的客户端可能都有一些延迟实例化代码。显而易见,它导致了大量重复代码。
理想状态下,我们可以直接在对象类中加入代码,但不总是这样。比如,这是第三方库中的类。
解决
Proxy模式建议创建一个和原服务对象遵循相同接口的替代类。当接收到客户端的请求时,代理对象创建一个服务对象的实例并把真正工作委托给他。
但是有什么好处呢?你可以在调用真正服务对象方法之前(或之后)在代理类中加入一些代码。因为代理和服务对象遵循相同的接口,它可以传递服务对象可以接受的任何代码。
现实世界类比
支票
支票是一种文件,可以命令银行从个人账户支付特定金额给持有支票的人。支票和现金都有一个通用的接口:可以用作付款。因此,支票是一大堆现金的代理。
站在消费者角度看,支票很棒,因为没有必要携带大量的现金。对于店主来说,他们也很好,因为可以在最近的银行兑换现金。
结构
Service interface为Service和Proxy生命了通用接口。
Service是一个提供有用业务逻辑的类。
Proxy有一个持有Service对象的字段。Proxy的方法做一些中间工作,并且大部分时间将请求传递给服务对象的相同方法。
大多情况下,Proxy管理它们的Service对象的声明周期。
Client应该同Service和Proxy通用接口协作。这样就可以把Proxy对象传递给任何期望一个Service对象的代码。
伪代码
在这个例子中,Proxy模式帮助实现懒实例化并且为一个低效的第三方Youtube集成库做缓存。
原视频下载类在之前已经下载过的情况下也会再去下载这个视频。代理类仅会使用原下载器下载一次相同的视频,然后把它缓存下来,随后相同的请求会直接返回缓存。
1 | // The interface of a remote service. |
适用性
懒初始化(虚拟代理)。当你有一个需要从文件系统,网络或者数据库加载数据的重量级对象时。
不是在应用启动时加载数据,而是将对象的初始化延迟到需要它的时间。
访问控制(保护代理)。当一个程序有不同类型的用户并且你想阻止未授权用户对保护对象的访问。比如,当对象是操作系统的关键部分,并且程序(包括恶意的)是他们的客户端。
代理会在每次请求时检查客户端的证书,只会让拥有正确访问权限请求通过。
本地执行一个远程服务(远程代理)。当一个真实服务对象在远程服务器上时。
在这种情况下,代理会把客户端的请求通过网络传递到远程服务,处理所有网络传输细节(译者注:Dubbo等RPC框架)。
缓存对象(智能引用)。当你需要混存客户端的请求并且管理它们的生命周期时(当结果比较重时)时。
Proxy能够统计引用服务对象或者缓存结果的数量。当所有的引用被释放,代理就可以销毁它追踪的对象(比如,终止数据库链接)。
Proxy还可以追踪客户端是否改变了服务对象。它可以重用未改变的对象并且保存系统资源。
请求日志(日志代理)。当你需要保留一个服务对象的请求历史。
Proxy可以在传递给服务对象之前记录下每次请求。
如何实现
从一个服务类抽象出代理和服务对象的通用接口。你可能需要创建一个服务类的子类作为被代理类,因为服务类可能是闭合的。
创建一个代理类。你应该用一个字段来持有服务对象的强引用。大多情况下,代理类自己创建它自己需要的服务对象。少数情况下,客户端通过构造代理类的构造方法把服务传递给代理。
根据目的来实现代理方法。大多情况下,在做一些事情后,代理类应该把工作委托给服务对象。
想想可以引入工厂对象来决定客户端需要什么样的对象,代理或真正的服务。另一方面,这个逻辑也可以在一个代理类中的一个创建方法中。
考虑添加懒初始化服务对象。这对比较重的对象很有用处。
优点
客户端无感知情况下控制对对象的访问。
可以在服务对象没有准备好时开始工作。
管理服务对象的生命周期,即使客户端并不关心。
缺点
- 延迟响应。
和其他模式的关系
Adapter提供不同的接口达到目的,而Proxy提供相同的接口。Decorator提供增强的接口。
Facade和Proxy很像,它们给复杂的实体提供缓冲并且初始化它。不像Facade,Proxy模式和服务对象遵循一样的接口,他们是通用的。
Decorator和Proxy结构相似,但目的不同。两者均采用组合的方式来把工作委托给其他对象。然而,Proxy自己管理服务对象的生命周期,而Decorator由客户端管理。
在Java中的使用
用例:虽然代理模式在大多数Java程序中不是常客,但在某些特殊情况下,它是非常方便的。当你想在一些现有的类的对象中添加一些额外的行为而不改变客户端代码时,它是不可取代的。
在标准Java库中有一些例子:
java.lang.reflect.Proxy
java.rmi.*
javax.ejb.EJB (注释)
javax.inject.Inject (注释)
javax.persistence.PersistenceContext
鉴定:代理将真正的工作全部委托给其他对象。每个代理方法应该最终引用一个服务对象,除非代理是服务的一个子类。