为其他对象提供一种代理以控制对这个对象的访问。
2. 别名
Surrogate
3. 动机
对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。我们考虑一个可以在文档中嵌入图形对象的文档编辑器。有些图形对象(如大型光栅图像)的创建开销很大。但是打开文档必须很迅速,因此我们在打开文档时应避免一次性创建所有开销很大的对象。因为并非所有这些对象在文档中都同时可见,所以也没有必要同时创建这些对象。
这一限制条件意味着,对于每一个开销很大的对象,应该根据需要进行创建,当一个图像变为可见时会产生这样的需要。但是在文档中我们用什么来代替这个图像呢?我们又如何
才能隐藏根据需要创建图像这一事实,从而不会使得编辑器的实现复杂化呢?例如,这种优化不应影响绘制和格式化的代码。
问题的解决方案是使用另一个对象,即图像 P r o x y,替代那个真正的图像。 P r o x y可以代替一个图像对象,并且在需要时负责实例化这个图像对象。
只有当文档编辑器激活图像代理的 Draw操作以显示这个图像的时候,图像 Proxy才创建真正的图像。 P r o x y直接将随后的请求转发给这个图像对象。因此在创建这个图像以后,它必须有一个指向这个图像的引用。
我们假设图像存储在一个独立的文件中。这样我们可以把文件名作为对实际对象的引用。P r o x y还存储了图像的尺寸(e x t e n t),即它的长和宽。有了图像尺寸, P r o x y无须真正实例化这个图像就可以响应格式化程序对图像尺寸的请求。以下的类图更详细地阐述了这个例子。
文档编辑器通过抽象的 G r a p h i c类定义的接口访问嵌入的图像。 I m a g e P r o x y是那些根据需要创建的图像的类, I m a g e P r o x y保存了文件名作为指向磁盘上的图像文件的指针。该文件名被作为一个参数传递给 ImageProxy的构造器。I m a g e P r o x y还存储了这个图像的边框以及对真正的 I m a g e实例的指引,直到代理实例化真正的图像时,这个指引才有效。 D r a w操作必须保证在向这个图像转发请求之前,它已经被实例化了。 G e t E x t e n t操作只有在图像被实例化后才向它传递请求,否则, I m a g e P r o x y返回它存储的图像尺寸。
4. 适用性
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用 P r o x y模式。下面是一些可以使用 Proxy模式常见情况:
1) 远 程 代 理 ( Remote Proxy ) 为 一 个 对 象在 不 同 的 地 址 空间 提 供 局 部 代 表。
NEXTSTEP[Add94] 使用N X P r o x y类实现了这一目的。 Coplien[Cop92] 称这种代理为“大使”(Ambassador)。
2) 虚代理(Virtual Proxy)根据需要创建开销很大的对象。
在动机一节描述的 ImageProxy就是这样一种代理的例子。
3) 保护代理(Protection Proxy)控制对原始对象的访问。
保护代理用于对象应该有不同的访问权限的时候。例如,在 C h o i c e s操作系统 [ C I R M 9 3 ]中K e m e l P r o x i e s为操作系统对象提供了访问保护。
4) 智能指引 (Smart Reference)取代了简单的指针,它在访问对象时执一些附加操作。
它的典型用途包括:
• 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它 (也称为S m a r t Pointers[Ede92])。
• 当第一次引用一个持久对象时,将它装入内存。
• 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
5. 结构
这是运行时刻一种可能的代理结构的对象图。
6. 参与者
• Proxy (ImageProxy)
— 保存一个引用使得代理可以访问实体。若 R e a l S u b j e c t和S u b j e c t的接口相同, P r o x y会引用Subject。
— 提供一个与 Subject的接口相同的接口,这样代理就可以用来替代实体。
— 控制对实体的存取,并可能负责创建和删除它。
— 其他功能依赖于代理的类型:
• Remote Pro x y负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
• Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问。例如,动机一节中提到的ImageProxy缓存了图像实体的尺寸。
• Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。
• Subject (Graphic)
— 定义RealSubject 和Proxy的共用接口,这样就在任何使用 RealSubject的地方都可以使用Proxy。
• RealSubject (Image)
— 定义Proxy所代表的实体。
7. 协作
• 代理根据其种类,在适当的时候向 RealSubject转发请求。
8. 效果
P r o x y模式在访问对象时引入了一定程度的间接性。根据代理的类型,附加的间接性有多种用途:
1) Remote Proxy可以隐藏一个对象存在于不同地址空间的事实。
2) Virtual Proxy 可以进行最优化,例如根据要求创建对象。
3) Protection Proxies和Smart Reference都允许在访问一个对象时有一些附加的内务处理(Housekeeping task)。
Proxy模式还可以对用户隐藏另一种称之为 c o p y - o n - w r i t e的优化方式,该优化与根据需要创建对象有关。拷贝一个庞大而复杂的对象是一种开销很大的操作,如果这个拷贝根本没有被修改,那么这些开销就没有必要。用代理延迟这一拷贝过程,我们可以保证只有当这个对象被修改的时候才对它进行拷贝。
在实现 C o p y - o n - w r i t e时必须对实体进行引用计数。拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时,代理才会真正的拷贝它。在这种情况下,代理还必须减少实体的引用计数。当引用的数目为零时,这个实体将被删除。Copy-on-Write可以大幅度的降低拷贝庞大实体时的开销。
9. 实现
Proxy模式可以利用以下一些语言特性:
1) 重载C++中的存取运算符
C++支持重载运算符 ->。重载这一运算符使你可以在撤消对一个对象的引用时,执行一些附加的操作。这一点可以用于实现某些种类的代理;代理的作用就象一个指针。
下面的例子说明怎样使用这一技术实现一个称为 ImagePtr的虚代理。
重载的->和*运算符使用 LoadImage将_image返回给它的调用者 (如果必要的话装入它 )。
该方法使你能够通过 I m a g e P t r对象调用 I m a g e操作,而省去了把这些操作作为 I m a g e P t r接口的一部分的麻烦。
请注意这里的 i m a g e代理起到一个指针的作用,但并没有将它定义为一个指向 I m a g e的指针。这意味着你不能把它当作一个真正的指向 I m a g e的指针来使用。因此在使用此方法时用户应区别对待 Image对象和Imageptr对象。
重载成员访问运算符并非对每一种代理来说都是好办法。有些代理需要清楚地知道调用了哪个操作,重载运算符的方法在这种情况下行不通。考虑在目的一节提到的虚代理的例子,图像应该在一个特定的时刻被装载 — 也就是在D r a w操作被调用时 — 而不是在只要引用这个图像就装载它。重载访问操作符不能作出这种区分。在这种情况下我们只能人工实现每一个代理操作,向实体转发请求。