代理(Proxy)模式給某一個對象提供一個代理,並由代理對象控制對原對象的引用。
代理模式的英文叫做Proxy或Surrogate,中文都可譯成"代理"。所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
二、代理的種類
如果按照使用目的來劃分,代理有以下幾種:
遠程(Remote)代理:為一個位於不同的地址空間的對象提供一個局域代表對象。這個不同的地址空間可以是在本機器中,也可是在另一台機器中。遠程代理又叫做大使(Ambassador)。
虛擬(Virtual)代理:根據需要創建一個資源消耗較大的對象,使得此對象只在需要時才會被真正創建。
Copy-on-Write代理:虛擬代理的一種。把復制(克隆)拖延到只有在客戶端需要時,才真正采取行動。
保護(Protect or Access)代理:控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用權限。
Cache代理:為某一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果。
防火牆(Firewall)代理:保護目標,不讓惡意用戶接近。
同步化(Synchronization)代理:使幾個用戶能夠同時使用一個對象而沒有沖突。
智能引用(Smart Reference)代理:當一個對象被引用時,提供一些額外的操作,比如將對此對象調用的次數記錄下來等。
在所有種類的代理模式中,虛擬(Virtual)代理、遠程(Remote)代理、智能引用代理(Smart Reference Proxy)和保護(Protect or Access)代理是最為常見的代理模式。
三、遠程代理的例子
Achilles是一個用來測試網站的安全性能的工具軟件。Achilles相當於位於客戶端的的一個桌面代理服務器,在一個HTTP過程裡起到一個中間人的作用,但是Achilles與通常的代理服務器又有不同。Achilles截獲雙向的通信數據,使得Achilles軟件的用戶可以改變來自和發往網絡服務器的數據,甚至可以攔截並修改SSL通訊。(這點在《Java與模式》中解釋的不是很清楚,關於對非對稱密鑰加密攔截、破解方法,可以參考我的另外一篇文章《通過代理截取並修改非對稱密鑰加密信息》)。
另外一個例子就是Windows的快捷方式。快捷方式是它所引用的程序的一個代理。
四、代理模式的結構
代理模式的類圖如下圖所示:
代理模式所涉及的角色有:
抽象主題角色(Subject):聲明了真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題。
代理主題(Proxy)角色:代理主題角色內部含有對真是主題的引用,從而可以在任何時候操作真實主題對象;代理主題角色提供一個與真實主題角色相同的接口,以便可以在任何時候都可以替代真實主體;控制真實主題的應用,負責在需要的時候創建真實主題對象(和刪除真實主題對象);代理角色通常在將客戶端調用傳遞給真實的主題之前或之後,都要執行某個操作,而不是單純的將調用傳遞給真實主題對象。
真實主題角色(RealSubject)角色:定義了代理角色所代表的真實對象。
五、代理模式示例性代碼
以下示例性代碼實現了代理模式:
// Proxy pattern -- Structural example
using System;
// "Subject"
abstract class Subject
{
// Methods
abstract public void Request();
}
// "RealSubject"
class RealSubject : Subject
{
// Methods
override public void Request()
{
Console.WriteLine("Called RealSubject.Request()");
}
}
// "Proxy"
class Proxy : Subject
{
// Fields
RealSubject realSubject;
// Methods
override public void Request()
{
// Uses "lazy initialization"
if( realSubject == null )
realSubject = new RealSubject();
preRequest();
realSubject.Request();
postRequest();
}
public void preRequest()
{ Console.WriteLine("PreRequest."); }
public void postRequest()
{ Console.WriteLine("PostRequest."); }
}
/**//// <summary>
/// Client test
/// </summary>
public class Client
{
public static void Main( string[] args )
{
// Create proxy and request a service
Proxy p = new Proxy();
p.Request();
}
}
六、高老莊悟空降八戒
盡管那時候八戒還不叫八戒,但為了方便,這裡仍然這樣稱呼他。
高老莊的故事
卻說那春融時節,悟空牽著白馬,與唐僧趕路西行。忽一日天色將晚,遠遠地望見一村人,這就是高老莊,豬八戒的丈人高太公家。為了將高家三小姐解救出八戒的魔掌,悟空決定扮做高小姐,會一會這個妖怪:
"行者卻弄神通,搖身一變,變得就如那女子一般,獨自個坐在房裡等那妖精。不多時,一陣風來,真個是走石飛砂……那陣狂風過處,只見半空裡來了一個妖精,果然生得丑陋:黑臉短毛,長喙大耳,穿一領青不青、藍不藍的梭布直裰,系一條花布手巾……走進房,一把摟住,就要親嘴……"
高家三小姐的神貌和本人
悟空的下手之處是將高家三小姐的神貌和她本人分割開來,這和"開一閉"原則有異曲同工之妙。這樣一來,"高家三小姐本人"也就變成了"高家三小姐神貌"的具體實現,而"高家三小姐神貌"則變成了抽象角色,如下圖所示。
悟空扮演並代替高家三小姐
悟空巧妙地實現了"高家三小姐神貌",也就是說同樣變成了"高家三小姐神貌"的子類。悟空可以扮演高家三小姐,並代替高家三小姐會見八戒,其靜態結構圖如下圖所示。
悟空代替"高家三小姐本人"去會見豬八戒。顯然這就是代理模式的應用。具體地講,這是保護代理模式的應用。只有代理對象認為合適時,才會將客戶端的請求傳遞給真實主題對象。
八戒分辨不出真假老婆
從《西游記》的描述可以看出,豬八戒根本份辨不出悟空扮演的"高家三小姐替身"和 "高家三小姐本人"。客戶端分辨不出代理主題對象與真實主題對象,這是代理模式的一個
重要用意。
悟空代替高家三小姐會見八戒的對象圖如下圖所示。
七、不同類型的代理模式
遠程代理
可以將網絡的細節隱藏起來,使得客戶端不必考慮網絡的存在。客戶完全可以認為被代理的對象是局域的而不是遠程的,而代理對象承擔了大部分的網絡通信工作,遠程代理的結構圖如下圖所示。
虛擬代理
使用虛擬代理模式的優點就是代理對象可以在必要的時候才將被代理的對象加載。代理可以對加載的過程加以必要的優化。當一個模塊的加載十分耗費資源的時候,虛擬代理的優點就非常明顯。
保護代理
保護代理可以在運行時間對用戶的有關權限進行檢查,然後在核實後決定將調用傳遞給被代理的對象。
智能引用代理
在訪問一個對象時可以執行一些內務處理(Housekeeping)操作,比如計數操作等。
八、代理模式實際應用的例子
該例子演示了利用遠程代理模式提供對另外一個應用程序域(AppDomain)的對象進行訪問控制。
// Proxy pattern -- Real World example
using System;
using System.Runtime.Remoting;
// "Subject"
public interface IMath
{
// Methods
double Add( double x, double y );
double Sub( double x, double y );
double Mul( double x, double y );
double Div( double x, double y );
}
// "RealSubject"
class Math : MarshalByRefObject, IMath
{
// Methods
public double Add( double x, double y ){ return x + y; }
public double Sub( double x, double y ){ return x - y; }
public double Mul( double x, double y ){ return x * y; }
public double Div( double x, double y ){ return x / y; }
}
// Remote "Proxy Object"
class MathProxy : IMath
{
// Fields
Math math;
// Constructors
public MathProxy()
{
// Create Math instance in a different AppDomain
AppDomain ad = System.AppDomain.CreateDomain("MathDomain",null, null );
ObjectHandle o = ad.CreateInstance("Proxy_RealWorld", "Math", false,
System.Reflection.BindingFlags.CreateInstance, null, null, null,null,null );
math = (Math) o.Unwrap();
}
// Methods
public double Add( double x, double y )
{
return math.Add(x,y);
}
public double Sub( double x, double y )
{
return math.Sub(x,y);
}
public double Mul( double x, double y )
{
return math.Mul(x,y);
}
public double Div( double x, double y )
{
return math.Div(x,y);
}
}
/**//// <summary>
/// ProxyApp test
/// </summary>
public class ProxyApp
{
public static void Main( string[] args )
{
// Create math proxy
MathProxy p = new MathProxy();
// Do the math
Console.WriteLine( "4 + 2 = {0}", p.Add( 4, 2 ) );
Console.WriteLine( "4 - 2 = {0}", p.Sub( 4, 2 ) );
Console.WriteLine( "4 * 2 = {0}", p.Mul( 4, 2 ) );
Console.WriteLine( "4 / 2 = {0}", p.Div( 4, 2 ) );
}
}