“ 设计二十三式之代理模式”
01 意图
代理是一种结构设计模式,可让您为另一个对象提供替代或占位符。代理控制对原始对象的访问,允许您在请求到达原始对象之前或之后执行某些操作。
02 问题
为什么要控制对对象的访问?这是一个示例:您有一个消耗大量系统资源的大型对象。您不时需要它,但并非总是如此。
您可以实现延迟初始化:仅在实际需要时创建此对象。所有对象的客户端都需要执行一些延迟初始化代码。不幸的是,这可能会导致大量代码重复。
在理想情况下,我们希望将此代码直接放入对象的类中,但这并不总是可行的。例如,该类可能是封闭的第 3 方库的一部分。
03 解决方案
代理模式建议您创建一个与原始服务对象具有相同接口的新代理类。然后更新您的应用程序,以便它将代理对象传递给原始对象的所有客户端。在收到来自客户端的请求后,代理会创建一个真实的服务对象并将所有工作委托给它。
但是有什么好处呢?如果您需要在类的主要逻辑之前或之后执行某些操作,代理允许您在不更改该类的情况下执行此操作。由于代理实现了与原始类相同的接口,因此可以将其传递给任何需要真实服务对象的客户端。
04 举个栗子
信用卡是银行账户的代理,银行账户是一捆现金的代理。两者都实现相同的接口:它们可用于付款。消费者感觉很好,因为不需要随身携带大量现金。店主也很高兴,因为交易收入以电子方式添加到商店的银行账户中,没有丢失存款或在去银行的路上被抢劫的风险。
05 结构实现
此示例说明代理模式如何帮助将延迟初始化和缓存引入 3rd 方 YouTube 集成库。
该库为我们提供了视频下载类。但是,它的效率非常低。如果客户端应用程序多次请求相同的视频,库只是一遍又一遍地下载它,而不是缓存和重用第一个下载的文件。
代理类实现与原始下载器相同的接口并将所有工作委托给它。但是,它会跟踪下载的文件并在应用程序多次请求相同的视频时返回缓存的结果。
// The interface of a remote service.
interface ThirdPartyYouTubeLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
// The concrete implementation of a service connector. Methods
// of this class can request information from YouTube. The speed
// of the request depends on a user's internet connection as
// well as YouTube's. The application will slow down if a lot of
// requests are fired at the same time, even if they all request
// the same information.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
method listVideos() is
// Send an API request to YouTube.
method getVideoInfo(id) is
// Get metadata about some video.
method downloadVideo(id) is
// Download a video file from YouTube.
// To save some bandwidth, we can cache request results and keep
// them for some time. But it may be impossible to put such code
// directly into the service class. 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()
06 适用场景
有很多方法可以利用代理模式。让我们回顾一下最流行的用途。
-
延迟初始化(虚拟代理)。这是当你有一个重量级的服务对象时,它总是在浪费系统资源,即使你只是不时需要它。
您可以将对象的初始化延迟到真正需要的时候,而不是在应用程序启动时创建对象。
-
访问控制(保护代理)。这是您只希望特定客户端能够使用服务对象的时候;例如,当您的对象是操作系统的关键部分而客户端是各种启动的应用程序(包括恶意应用程序)时。
仅当客户端的凭据匹配某些条件时,代理才能将请求传递给服务对象。
-
远程服务的本地执行(远程代理)。这是当服务对象位于远程服务器上时。
在这种情况下,代理通过网络传递客户端请求,处理与网络合作的所有令人讨厌的细节。
-
记录请求(记录代理)。这是您想要保留对服务对象的请求历史记录的时候。
代理可以在将每个请求传递给服务之前记录它。
-
缓存请求结果(缓存代理)。这是当您需要缓存客户端请求的结果并管理此缓存的生命周期时,尤其是在结果非常大的情况下。
代理可以为总是产生相同结果的重复请求实现缓存。代理可以使用请求的参数作为缓存键。
-
智能参考。这是您需要能够在没有使用它的客户端时关闭重量级对象的时候。
代理可以跟踪获得对服务对象或其结果的引用的客户端。有时,代理可能会检查客户端并检查它们是否仍然处于活动状态。如果客户端列表为空,代理可能会关闭服务对象并释放底层系统资源。 代理还可以跟踪客户端是否修改了服务对象。然后未更改的对象可能会被其他客户端重用。
07 如何实施
如果没有预先存在的服务接口,则创建一个以使代理和服务对象可互换。从服务类中提取接口并不总是可行的,因为您需要更改所有服务的客户端才能使用该接口。方案 B 是让代理成为服务类的子类,这样它就会继承服务的接口。
创建代理类。它应该有一个用于存储对服务的引用的字段。通常,代理创建和管理其服务的整个生命周期。在极少数情况下,客户端会通过构造函数将服务传递给代理。
根据其目的实现代理方法。在大多数情况下,代理完成一些工作后,应该将工作委托给服务对象。
考虑引入一种创建方法来决定客户端是获得代理还是真正的服务。这可以是代理类中的简单静态方法,也可以是成熟的工厂方法。
考虑为服务对象实现延迟初始化。
08 优缺点