代理(Proxy)模式

来源:互联网 发布:如何下载spss软件 编辑:程序博客网 时间:2024/04/25 09:49
摘要
本文讨论了代理模式的概念、种类、长处和短处,以及在Visual Basic.NET(见文中)和C#语言中的实现(见附录),以及代理模式与其他设计模式的关系。

目录
代理模式概念
代理的种类
远程代理的例子:Achilles
Windows的快捷方式:代理的例子
代理模式的结构
代理模式的时序
代理模式的长处和短处
模式的实现
代理模式与其它模式的关系
附录、C#代码
参考文献

代理模式概念
代理模式是对象的结构模式[GOF95]。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
代理模式的英文叫做Proxy或Surrogate,中文都可译成"代理"。所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。 在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理的种类
如果按照使用目的来划分,代理有以下几种:
远程(Remote)代理、为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,亦可是在另一台机器中。远程代理又叫做大使(Ambassador)。
虚拟(Virtual)代理、根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。本章下面给出一个加载图像的例子说明虚拟代理的使用。
Copy-on-Write代理、虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
保护(Protect or Access)代理、控制对一个对象的访问,如果需要可以给不同的用户提供不同级别的使用权限。
Cache代理、为某一个目标操作的结果提供临时的储存空间,以便多个客户端可以共享这些结果。
防火墙(Firewall)代理、保护目标,不让恶意用户接近。
同步化(Synchronization)代理、使几个用户能够同时使用一个对象而没有冲突。
智能引用(Smart Reference)代理、当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(Smart Reference Proxy)和保护(Protect or Access)代理是最为常见的代理模式种类。
在有些讲解设计模式的书籍中(如[GRAND98]),不同的代理模式被独立划分出来,以强调它们的不同;在另外的书籍中,所有的代理模式都放在一些讲解,以强调它们的共同点。本文采取[YAN02]中的讲解方式,首先将所有的代理模式放到一个地方进行理论上的讲解,然后针对不同的类型提供不同的例子,进行实现方式上的讲解。

远程代理的例子:Achilles
Achilles是一个用来测试网站的安全性能的工具软件。Achilles相当于位于客户端的一个桌面代理服务器,在一个HTTP过程里起到一个中间人的作用,但是Achilles与通常的代理服务器又有不同。
一个通常的HTTP代理软件会将一个客户端的HTTP数据包转发给网络服务器。Achilles则截获双向的通讯数据,使得Achilles软件的用户可以改变来自和发往网络服务器的数据。比如,在一个正常的SSL联系中,一个通常的代理服务器会转发通讯使得双方可以商议SSL连接;而Achilles则不同。当Achilles处于截取状态时,它会向客户端假装是服务器,同时向真正的服务器假装是浏览器,在两端商议SSL通讯。Achilles可以破解加密的数据,给Achilles的用户显示已经解密的内容,并且允许用户更改处于通讯过程中的数据。
下面显示的是Achilles软件在运行时的情况:

图1、Achilles运行的情况。
读者可以免费从http://www.digizen-security.com/projects.html下载这个软件。
显然,对于浏览器而言,Achilles所代理的是远程的网络服务器。Achilles的工作方式便是远程代理模式的应用。

Windows的快捷方式:代理的例子
Windows系统提供快捷方式(Shortcut),可以使任何对象同时出现在多个地方而不必修改原对象。对快捷方式的调用完全与对原对象的调用一样,换言之,快捷方式对客户端是完全透明的。

图2、服务的快捷方式。
比如上面的图标便是Windows服务的代理。所有的快捷方式都有一个小的箭头在图标的左下方。这就是说,用户可以区分原对象和指向原对象的代理。如果原对象被删除,则快捷方式虽然仍可存在,但是在调用时会给出错误。
在下面的图中,一个名为link1的快捷方式是一个名为explorer.exe的文件的代理。当用户启动这个快捷方式时,link1会把用户的调用传递给它所代理的explorer.exe文件。

图3、一个快捷方式的例子。
在Macintosh里面有Alias,在Unix里面有link,它们都和Windows的便捷方式一样,是代理模式的应用。

代理模式的结构
代理模式所涉及的角色有:
抽象主题角色、声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以使用代理主题。
代理主题(Proxy)角色、首先代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;
其次代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主体;
第三,控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);
第四,代理角色通常在将客户端调用传递给真实的主题之前或者之后都要执行某个操作,而不是单纯地将调用传递给真实主题对象。
真实主题角色、定义了代理角色所代表的真实对象。
下面给出一个非常简单的示意性实现,请见实现的类图:

图4、代理模式的结构图。
下面给出的是抽象主题角色的示意性源代码,可以看出,这个角色规定所有的主题对象必须实现request()方法:
Public MustInherit Class Subject    Public MustOverride Sub Request()End Class
代码清单1、主题角色的源代码。
下面就是具体主题角色的示意性源代码。这里仅仅给出了request()方法的示意性实现:
Public Class RealSubject    Inherits Subject    Public Sub RealSubject()        System.Console.WriteLine("RealSubject object is created.")    End Sub    Public Overrides Sub Request()        System.Console.WriteLine("RealSubject.Request().")    End SubEnd Class
代码清单2、真实主题角色的源代码。
下面是代理主题角色的源代码。可以看出,代理主题除了将所有的请求原封不动地委派给真实主题角色之外,还在委派之前和之后分别执行一个preRequest()操作和一个postRequest()操作:
Public Class ProxySubject    Inherits Subject    Private rs As RealSubject    Public Sub New()        rs = New RealSubject()    End Sub    Private Sub PreRequest()        System.Console.WriteLine("Before passing request to RealSubject.")    End Sub    Private Sub PostRequest()        System.Console.WriteLine("After passing request to RealSubject.")    End Sub    Public Overrides Sub Request()        PreRequest()        rs.Request()        PostRequest()    End SubEnd Class
代码清单3、代理主题的源代码。
在使用代理主题时,注意要将变量的明显类型声明为抽象主体的类型,而将其真实类型设置为代理主题类型。请见下面的例子:
    Private subj As Subject    . . .     subj = New ProxySubject()    subj.Request()
代码清单4、怎样调用代理主题。
在运行时,会打印出下面的信息:
Before passing request to RealSubject.
RealSubject.Request().
After passing request to RealSubject.
代码清单5、怎样调用代理主题。
从上面的代理主题类的示意性源代码可以看出代理模式是怎样工作的。首先,代理主题并不改变主题的接口,因为模式的用意是不让客户端感觉到代理的存在;其次,代理使用委派将客户端的调用委派给真实的主题对象,换言之,代理主题起到的是一个传递请求的作用;第三,代理主题在传递请求之前和之后都可以执行特定的操作,而不是单纯传递请求。

代理模式的时序
类图是静态的并不适合于反映出模式在运行时的特性;时序图更能够反映出模式的活动情况。下面就是所讨论的代理模式的时序图:

图5、代理模式的时序图。
从上面的时序图可以看出,客户端向代理主题发出请求,代理主题在接到请求的同时,执行了一个PreRequest()操作,然后才把请求传递给真实主题。在真实主题将请求返回后,代理主题又执行了一个PostRequest()操作,才将控制返回给客户端。参见下面的对象图。

图6、代理模式的对象图。
与客户端直接向真实主题发出请求的情况相比,使用代理主题的显著好处是系统提供了向真实主题传递客户端请求的控制。代理主题可以在传递向真实主题传递客户端请求之前执行特定的操作,并决定是否将请求传递给真实主题;代理主题可以在向真实主题传递客户端请求之后执行另外一种操作,比如将客户端请求计数等。有可能客户端根本没有直接向真实主题提出请求的许可,而代理主题具有这种许可,因此代理主题可以在执行PreRequest()操作后决定是否再传递请求,等等。总之,代理模式将一个中间层插入到客户端和主题角色之间,从而提供了许多的灵活性。

代理模式的长处和短处
根据代理的种类代理模式有不同的特点。
远程代理
好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部份的网络通讯工作。

图7、远程代理的结构图。
由于客户可能没有意识到会启动一个耗费时间的远程调用,因此客户没有必要的思想准备。
虚拟代理
使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载;代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。
在后面的章节中会给出一个详细的例子,读者可以从中学习到虚拟代理的实现方式。
保护代理
保护代理的好处是它可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用传递给被代理的对象。
智能引用代理
在访问一个对象时可以执行一些内务处理(Housekeeping)操作,比如计数操作等。
在后面的章节中会给出一个例子,供读者学习智能引用代理的实现细节。

模式的实现
代理模式可能并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口。这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其它角色代为创建并且传入。实际上这种做法可以提供更大的灵活性。
Java 2.0所提供的对代理模式的支持就是采取了这种做法,请参见本章的"Java 2.0对代理模式的支持"一节。

代理模式与其它模式的关系
代理模式与变压器模式等其它模式有关系。
变压器模式
粗看上去,变压器模式与代理模式很相像,它们都视为一个对象提供一种前置的接口。但是,变压器模式的用意是要改变所考虑的对象的接口;而代理模式并不能改变所代理的对象的接口,在这一点上两个模式有明显的区别。

图8、变压器模式的简略类图。左边是类的变压器模式,右边是对象的变压器模式。
装饰模式
装饰模式与所装饰的对象具有相同的接口,因此这两种模式也有可能混淆。但是装饰模式应当为所装饰的对象提供增强功能;而代理模式对对象的使用施加控制,并不提供对象本身的增强功能。

图9、装饰模式的简略类图。
门面模式
有的时候门面模式兼任代理的责任。门面对象可能是一个位于另一个地址空间的子系统的远程代理;有的时候门面模式兼任保护代理的角色,检查调用者的权限;有的时候门面模式负责记录子系统被调用的次数,因此兼任智能引用代理的角色;有的时候门面模式兼任虚拟代理的角色,特别是当子系统的加载耗费时间和资源的时候。
这种时候,门面模式又叫做代理门面模式,或门面代理模式。

图10、门面模式的简略类图。

附录、C#代码
下面给出本文中所有代码的C#语言版本。首先代码清单1-3对应为
using System;namespace Proxy{public interface ISubject{void request();}public class ProxySubject : ISubject{private RealSubject _real;public ProxySubject(){_real = new RealSubject();}public void request(){preprocess();_real.request();postprocess();}private void preprocess(){System.Console.WriteLine("Before handling request.");}private void postprocess(){System.Console.WriteLine("After handling request.");}}public class RealSubject : ISubject{public RealSubject(){System.Console.WriteLine("RealSubject is instantiated.");}public void request(){System.Console.WriteLine("RealSubject.Request().");}}}
而代码清单4对应为:
private ProxySubject proxy;。 。 。 proxy = new ProxySubject();proxy.request();

参考文献
[GOF95] E.Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns - Elements of Reusable Object-Oriented Software, 1995, Addison-Wesley.
[GRAND98] Mark Grand, Patterns in Java, Vol. 1, John Wiley & Sons, Inc., 1998.
[YAN02] 阎宏,Java与模式,中国电子工业出版社2002年10月1日出版

作者简介
阎宏,1964年出生于天津市。1987年毕业于中国科技大学近代物理系,1990年于中科院理论物理所获硕士学位,1992年获博士学位,翌年赴日本京都大学进行博士后研究工作。
作者曾于美国花旗银行(Citibank)、美国汤臣金融(Thomson Financial)、美国奥本海默基金(Oppenheimer)等处供职,进行了多年的软件开发、架构设计和技术管理工作。现任中国蓝数据公司技术总监。

版权声明
凡在本网站发表的文章,作者必须保证是原创文章;如果不是原创文章,由此带来的版权纠纷由作者自己承担。微软有义务对本站点发表的文章进行审核,但您需要考虑这些文章的使用风险。本站点仅提供一个用户相互交换技术信息和学习的园地,并不对来自社区用户的技术文章承担任何风险。
原创粉丝点击