代理模式概念
代理模式是对象的结构模式[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软件在运行时的情况:
读者可以免费从http://www.digizen-security.com/projects.html下载这个软件。
显然,对于浏览器而言,Achilles所代理的是远程的网络服务器。Achilles的工作方式便是远程代理模式的应用。
Windows的快捷方式:代理的例子
Windows系统提供快捷方式(Shortcut),可以使任何对象同时出现在多个地方而不必修改原对象。对快捷方式的调用完全与对原对象的调用一样,换言之,快捷方式对客户端是完全透明的。
比如上面的图标便是Windows服务的代理。所有的快捷方式都有一个小的箭头在图标的左下方。这就是说,用户可以区分原对象和指向原对象的代理。如果原对象被删除,则快捷方式虽然仍可存在,但是在调用时会给出错误。
在下面的图中,一个名为link1的快捷方式是一个名为explorer.exe的文件的代理。当用户启动这个快捷方式时,link1会把用户的调用传递给它所代理的explorer.exe文件。
在Macintosh里面有Alias,在Unix里面有link,它们都和Windows的便捷方式一样,是代理模式的应用。
代理模式的结构
代理模式所涉及的角色有:
抽象主题角色、声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以使用代理主题。
代理主题(Proxy)角色、首先代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;
其次代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主体;
第三,控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);
第四,代理角色通常在将客户端调用传递给真实的主题之前或者之后都要执行某个操作,而不是单纯地将调用传递给真实主题对象。
真实主题角色、定义了代理角色所代表的真实对象。
下面给出一个非常简单的示意性实现,请见实现的类图:
下面给出的是抽象主题角色的示意性源代码,可以看出,这个角色规定所有的主题对象必须实现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 Sub
End 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 Sub
End 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、怎样调用代理主题。
从上面的代理主题类的示意性源代码可以看出代理模式是怎样工作的。首先,代理主题并不改变主题的接口,因为模式的用意是不让客户端感觉到代理的存在;其次,代理使用委派将客户端的调用委派给真实的主题对象,换言之,代理主题起到的是一个传递请求的作用;第三,代理主题在传递请求之前和之后都可以执行特定的操作,而不是单纯传递请求。
代理模式的时序
类图是静态的并不适合于反映出模式在运行时的特性;时序图更能够反映出模式的活动情况。下面就是所讨论的代理模式的时序图:
从上面的时序图可以看出,客户端向代理主题发出请求,代理主题在接到请求的同时,执行了一个PreRequest()操作,然后才把请求传递给真实主题。在真实主题将请求返回后,代理主题又执行了一个PostRequest()操作,才将控制返回给客户端。参见下面的对象图。
与客户端直接向真实主题发出请求的情况相比,使用代理主题的显著好处是系统提供了向真实主题传递客户端请求的控制。代理主题可以在传递向真实主题传递客户端请求之前执行特定的操作,并决定是否将请求传递给真实主题;代理主题可以在向真实主题传递客户端请求之后执行另外一种操作,比如将客户端请求计数等。有可能客户端根本没有直接向真实主题提出请求的许可,而代理主题具有这种许可,因此代理主题可以在执行PreRequest()操作后决定是否再传递请求,等等。总之,代理模式将一个中间层插入到客户端和主题角色之间,从而提供了许多的灵活性。
代理模式的长处和短处
根据代理的种类代理模式有不同的特点。
远程代理
好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担