代理
意图
代理是一种结构设计模式,允许您为另一个对象提供替代品或占位符。代理控制对原始对象的访问,允许您在请求到达原始对象之前或之后执行某些操作。

问题
为什么要控制对对象的访问?这里有一个例子:您有一个消耗大量系统资源的大型对象。你不时地需要它,但并非总是如此。

数据库查询可能非常慢。
您可以实现惰性初始化:仅在实际需要时创建该对象。对象的所有客户端都需要执行一些延迟的初始化代码。不幸的是,这可能会导致大量代码重复。
在理想情况下,我们希望将此代码直接放入对象的类中,但这并不总是可能的。例如,类可能是一个封闭的第三方库的一部分。
解决方案
代理模式建议您创建一个具有与原始服务对象相同接口的新代理类。然后更新应用程序,使其将代理对象传递给原始对象的所有客户端。在收到来自客户端的请求后,代理将创建一个真正的服务对象,并将所有工作委托给它。

代理将自己伪装成数据库对象。它可以处理延迟初始化和结果缓存,而客户机或真正的数据库对象甚至不知道。
但是有什么好处呢?如果您需要在类的主要逻辑之前或之后执行某项操作,代理可以让您在不更改该类的情况下执行该操作。由于代理实现了与原始类相同的接口,因此可以将它传递给需要实际服务对象的任何客户机。
真实的模拟

信用卡和现金一样可以用来支付。
信用卡是银行账户的代理,银行账户是一捆现金的代理。两者都实现了相同的接口:它们可以用于支付。消费者感觉很棒,因为他们不需要随身携带大量现金。店主也很高兴,因为交易收入可以通过电子方式存入商店的银行账户,而不用担心丢失存款或在去银行的路上被抢劫。
结构

的服务接口声明服务的接口。代理必须遵循此接口才能将自己伪装成服务对象。
的服务是提供一些有用业务逻辑的类。
的代理类具有指向服务对象的引用字段。代理完成其处理(例如,延迟初始化、日志记录、访问控制、缓存等)后,它将请求传递给服务对象。
通常,代理管理其服务对象的完整生命周期。
的客户端应该通过相同的接口与服务和代理一起工作。通过这种方式,您可以将代理传递到需要服务对象的任何代码中。
伪代码
这个例子说明了如何代理模式可以帮助引入惰性初始化和缓存到第三方YouTube集成库。

使用代理缓存服务的结果。
图书馆为我们提供了视频下载课程。然而,它的效率非常低。如果客户端应用程序多次请求相同的视频,库只是一遍又一遍地下载它,而不是缓存和重用第一次下载的文件。
代理类实现了与原始下载器相同的接口,并将所有工作委托给它。但是,它会跟踪下载的文件,并在应用程序多次请求相同的视频时返回缓存的结果。
//远程服务接口。接口ThirdPartyYouTubeLib is方法listVideos()方法getVideoInfo(id)方法downloadVideo(id) //服务连接器的具体实现。该类的//方法可以从YouTube请求信息。请求的速度取决于用户的互联网连接以及YouTube的网络连接。如果同时触发很多//请求,即使它们都请求//相同的信息,应用程序也会变慢。类ThirdPartyYouTubeClass实现ThirdPartyYouTubeLib方法listVideos()是//发送API请求到YouTube。method getVideoInfo(id) is //获取视频的元数据。method downloadVideo(id) is //从YouTube下载视频文件。//为了节省带宽,我们可以缓存请求结果,并将它们保存一段时间。但是将这样的代码//直接放入服务类中可能是不可能的。 For example, it could have // been provided as part of a third party library and/or defined // as `final`. That's why we put the caching code into a new // proxy class which implements the same interface as the // service class. It delegates to the service object only when // the real requests have to be sent. class CachedYouTubeClass implements ThirdPartyYouTubeLib is private field service: ThirdPartyYouTubeLib private field listCache, videoCache field needReset constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is this.service = service method listVideos() is if (listCache == null || needReset) listCache = service.listVideos() return listCache method getVideoInfo(id) is if (videoCache == null || needReset) videoCache = service.getVideoInfo(id) return videoCache method downloadVideo(id) is if (!downloadExists(id) || needReset) service.downloadVideo(id) // The GUI class, which used to work directly with a service // object, stays unchanged as long as it works with the service // object through an interface. We can safely pass a proxy // object instead of a real service object since they both // implement the same interface. class YouTubeManager is protected field service: ThirdPartyYouTubeLib constructor YouTubeManager(service: ThirdPartyYouTubeLib) is this.service = service method renderVideoPage(id) is info = service.getVideoInfo(id) // Render the video page. method renderListPanel() is list = service.listVideos() // Render the list of video thumbnails. method reactOnUserInput() is renderVideoPage() renderListPanel() // The application can configure proxies on the fly. class Application is method init() is aYouTubeService = new ThirdPartyYouTubeClass() aYouTubeProxy = new CachedYouTubeClass(aYouTubeService) manager = new YouTubeManager(aYouTubeProxy) manager.reactOnUserInput()
适用性
有几十种方法可以利用代理模式。让我们来看看最流行的用法。
延迟初始化(虚拟代理)。这是指当您拥有一个重量级服务对象时,尽管您只是偶尔需要它,但它总是处于正常状态,从而浪费了系统资源。
你可以将对象的初始化延迟到真正需要的时候,而不是在应用程序启动时创建对象。
访问控制(保护代理)。这是当您希望只有特定的客户端能够使用服务对象时;例如,当您的对象是操作系统的关键部分,而客户端是各种启动的应用程序(包括恶意应用程序)时。
只有当客户端的凭据符合某些条件时,代理才能将请求传递给服务对象。
远程服务(远程代理)的本地执行。这是当服务对象位于远程服务器上时。
在这种情况下,代理通过网络传递客户端请求,处理与网络一起工作的所有讨厌的细节。
日志请求(日志代理)。这是当您希望保存对服务对象的请求历史时。
代理可以在将每个请求传递给服务之前记录该请求。
缓存请求结果(缓存代理)。这时您需要缓存客户机请求的结果并管理此缓存的生命周期,特别是当结果相当大时。
代理可以为总是产生相同结果的重复请求实现缓存。代理可以使用请求的参数作为缓存键。
智能参考。此时,您需要能够在没有客户端使用重量级对象时取消它。
代理可以跟踪获得对服务对象或其结果的引用的客户机。代理可能会不时地遍历客户端并检查它们是否仍处于活动状态。如果客户端列表为空,代理可能会取消服务对象并释放底层系统资源。
代理还可以跟踪客户端是否修改了服务对象。然后,未更改的对象可以被其他客户端重用。
如何实施
如果没有预先存在的服务接口,可以创建一个使代理和服务对象可互换的接口。从服务类中提取接口并不总是可行的,因为您需要更改所有服务的客户端才能使用该接口。计划B是使代理成为服务类的子类,这样它将继承服务的接口。
创建代理类。它应该有一个字段用于存储对服务的引用。通常,代理创建并管理其服务的整个生命周期。在极少数情况下,客户端通过构造函数将服务传递给代理。
根据它们的目的实现代理方法。在大多数情况下,代理在完成一些工作后,应该将工作委托给服务对象。
考虑引入一种创建方法,该方法决定客户端获得的是代理还是实际服务。这可以是代理类中的一个简单静态方法,也可以是一个成熟的工厂方法。
考虑为服务对象实现延迟初始化。
利与弊
-
- 您可以在客户机不知道的情况下控制服务对象。
-
- 当客户端不关心服务对象时,您可以管理服务对象的生命周期。
-
- 即使服务对象还没有准备好或不可用,代理也可以工作。
-
- 打开/关闭原则.您可以在不更改服务或客户机的情况下引入新的代理。
-
- 由于需要引入许多新类,代码可能会变得更加复杂。
-
- 来自服务的响应可能会延迟。