文章

23种设计模式-代理模式

Proxy模式 代理模式

Intent/目的

提供另一个对象的替代品或占位符,以控制对它的访问。

Also Known As

Surrogate/替代品

Motivation/动机

控制对一个对象访问的一个原因是,将其创建和初始化的全部成本延后,直到我们实际需要使用它时再进行。考虑一个可以在文档中嵌入图形对象的文档编辑器。一些图形对象,比如大型光栅图像,创建起来可能非常昂贵。但是打开文档应该是快速的,所以我们应该避免在文档打开时一次性创建所有昂贵的对象。无论如何,这并不是必要的,因为并非所有这些对象都会在同一时间在文档中可见。

这些限制表明,应该在需要时创建每个成本较高的对象,在这种情况下,就是当图像变得可见时。但是,我们应该在文档中代替图像放置什么呢?我们如何隐藏图像是按需创建的事实,以免使编辑器的实现复杂化呢?例如,这种优化不应影响渲染和格式化代码。

解决方案是使用另一个对象,即图像代理,它充当真实图像的替代品。代理的行为就像图像一样,并在需要时负责实例化它。

图像代理仅在文档编辑器要求其通过调用其绘制操作来显示自身时才创建真实图像。代理将后续请求直接转发给图像。因此,它在创建图像后必须保留对图像的引用。

假设图像存储在单独的文件中。在这种情况下,我们可以使用文件名作为对真实对象的引用。代理还存储其大小,即其宽度和高度。大小使代理能够在不实际实例化图像的情况下,响应格式化程序对其尺寸的请求。

以下类图更详细地说明了这个例子:

文档编辑器通过抽象的Graphic类定义的接口访问嵌入的图像。ImageProxy是一个按需创建图像的类。ImageProxy保持文件名作为对磁盘上图像的引用。文件名作为参数传递给ImageProxy构造函数。

ImageProxy还存储图像的边界框和对真实图像实例的引用。这个引用在代理实例化真实图像之前是无效的。绘制操作确保在转发请求之前图像被实例化。如果图像已经被实例化,GetExtent会将请求转发给图像;其他情况,ImageProxy返回它存储的尺寸。

Applicability/应用场景

代理适用于需要比简单指针更灵活或更复杂的对象引用的情况。以下是几种常见的代理模式适用的情况:

  1. 远程代理(remote proxy)为位于不同地址空间中的对象提供一个本地代表。
  2. 虚拟代理(virtual proxy)按需创建成本较高的对象。在上面的Motivation部分描述的ImageProxy就是这样一个代理的例子。
  3. 保护代理(protection proxy)控制对原始对象的访问。当对象应该具有不同的访问权限时,保护代理非常有用。
  4. 智能引用(smart refrence)是裸指针的替代品,当访问对象时,它执行额外的操作。典型用途包括:
    • 计算对真实对象的引用数量,以便在没有更多引用时可以自动释放它(也称为智能指针(smart pointer))。
    • 当首次引用时,将一个持久对象加载到内存中。
    • 在访问真实对象之前检查它是否被锁定,以确保没有其他对象可以改变它。

Structure/结构

Participants/角色

  • Proxy (ImageProxy)
    • 保持一个引用,让代理可以访问真实主体。如果RealSubjectSubject接口相同,代理可能会引用一个Subject
    • 提供一个与Subject相同的接口,以便可以用代理替代真实主体。
    • 控制对真实主体的访问,并可能负责创建和删除它。
    • 其他职责取决于代理的类型:
      • remote proxy, 负责对请求及其参数进行编码,并将编码后的请求发送到位于不同地址空间中的真实主体。
      • virtual proxy, 可能会缓存有关真实主体的额外信息,以便它们可以延迟访问。例如,Motivation部分提到的ImageProxy会缓存真实图像的尺寸。
      • protection proxy, 检查调用者是否具有执行请求所需的访问权限。
  • Subject (Graphic)
    • 定义了RealSubjectProxy的共同接口,以便在期望RealSubject的任何地方都可以使用Proxy
  • RealSubject (Image)
    • 定义了代理所代表的真实对象。

Consequences/总结

代理模式在访问对象时引入了一个间接层。这个额外的间接层有许多用途,具体取决于代理的类型:

  1. remote proxy, 远程代理可以隐藏对象位于不同地址空间的事实。
  2. virtual proxy, 虚拟代理可以执行优化,例如按需创建对象。
  3. protection proxy 和 smart referance, 保护代理和智能引用允许在访问对象时进行额外的管理任务。

代理模式还可以隐藏客户端不需要知道的另一种优化。它被称为写时复制(copy-on-write),与按需创建有关。复制一个大型和复杂的对象可能是一个昂贵的操作。如果副本从未被修改,则没有必要承担这个成本。通过使用代理延迟复制过程,我们确保只有在修改对象时才支付复制成本。

要使写时复制工作,主体必须被引用计数。复制代理将不会做更多事情,除了增加这个引用计数。只有当客户端请求修改主体的操作时,代理实际上才复制它。在这种情况下,代理还必须减少主体的引用计数。当引用计数降至零时,主体被删除。

写时复制可以显著降低复制重量级主体的成本。

适配器(Adapter):适配器为它适配的对象提供一个不同的接口。相比之下,代理提供与其主体相同的接口。然而,用于访问保护的代理可能拒绝执行主体将要执行的操作,因此其接口实际上可能是主体接口的一个子集。

装饰器(Decorator):尽管装饰器和代理在实现上可能相似,但装饰器有着不同的目的。装饰器向对象添加一个或多个责任,而代理则控制对一个对象的访问。

代理在实现上与装饰器的相似程度各不相同。保护代理可能完全像装饰器那样实现。另一方面,远程代理不会包含对其真实主体的直接引用,而只有一个间接引用,比如“主机ID和主机上的本地地址”。虚拟代理最初会使用一个间接引用,比如文件名,但最终会获得并使用一个直接引用。

本文由作者按照 CC BY 4.0 进行授权

© Poul.Y. 保留部分权利。