引子:替朋友代送禮物#
戴勵高中時喜歡同班的嬌嬌,但他不直接表白,而是說「我幫好朋友卓賈易代送禮物」——芭比娃娃、鮮花、巧克力,全都假借「卓賈易」的名義送出。最後嬌嬌反過來告白:「我願意和你做朋友!」。事實上,根本沒有卓賈易這個人。
戴勵就是「卓賈易」的代理人——這正是代理模式的生活對應。
第一版:沒有代理的程式#
讓「追求者」直接送禮給「被追求者」:
class SchoolGirl { public string Name { get; set; } }
class Pursuit
{
SchoolGirl mm;
public Pursuit(SchoolGirl mm) { this.mm = mm; }
public void GiveDolls() { Console.WriteLine($"{mm.Name} 送你洋娃娃"); }
public void GiveFlowers() { Console.WriteLine($"{mm.Name} 送你鮮花"); }
public void GiveChocolate() { Console.WriteLine($"{mm.Name} 送你巧克力"); }
}嬌嬌並不認識卓賈易。這樣寫等於追求者親自送禮,與故事不符。
第二版:只有代理的程式#
把 Pursuit 改成 Proxy:
class Proxy
{
SchoolGirl mm;
public Proxy(SchoolGirl mm) { this.mm = mm; }
public void GiveDolls() { ... }
}這變成「禮物是戴勵送的」——但事實是禮物是卓賈易買的,戴勵只是代送。
第三版:符合實際的代碼#
真正的代理:被代理的人(Pursuit)和代理人(Proxy)都實作相同的介面,因此 Proxy 可以替代 Pursuit。
interface GiveGift
{
void GiveDolls();
void GiveFlowers();
void GiveChocolate();
}
class Pursuit : GiveGift
{
SchoolGirl mm;
public Pursuit(SchoolGirl mm) { this.mm = mm; }
public void GiveDolls() { ... }
public void GiveFlowers() { ... }
public void GiveChocolate() { ... }
}
class Proxy : GiveGift
{
Pursuit gg;
public Proxy(SchoolGirl mm) { gg = new Pursuit(mm); }
public void GiveDolls() => gg.GiveDolls();
public void GiveFlowers() => gg.GiveFlowers();
public void GiveChocolate() => gg.GiveChocolate();
}客戶端:
SchoolGirl jiaojiao = new SchoolGirl { Name = "嬌嬌" };
Proxy daili = new Proxy(jiaojiao);
daili.GiveDolls();
daili.GiveFlowers();
daili.GiveChocolate();嬌嬌不認識追求她的人,卻可以透過代理人收到禮物。
代理模式#
代理模式(Proxy Pattern):為其他物件提供一種代理以控制對這個物件的訪問。[DP]
結構#
- Subject:定義 RealSubject 與 Proxy 的共用介面,使得任何可使用 RealSubject 的地方都能換成 Proxy
- RealSubject:定義 Proxy 所代表的真實實體
- Proxy:保存一個對 RealSubject 的引用,並提供與 Subject 相同的介面,因此可替代真實實體
classDiagram
class Subject {
<<abstract>>
+Request()
}
class RealSubject {
+Request()
}
class Proxy {
-RealSubject realSubject
+Request()
}
class Client
Subject <|-- RealSubject
Subject <|-- Proxy
Proxy o--> RealSubject
Client ..> Subjectabstract class Subject
{
public abstract void Request();
}
class RealSubject : Subject
{
public override void Request() => Console.WriteLine("真實的請求");
}
class Proxy : Subject
{
RealSubject realSubject;
public override void Request()
{
if (realSubject == null) realSubject = new RealSubject();
realSubject.Request();
}
}代理模式的應用#
1. 遠端代理(Remote Proxy)#
為一個物件在不同地址空間提供局部代表,以隱藏物件存在於不同地址空間的事實。[DP]
例如 .NET 中的 WebService:在專案加入 Web Reference 時,會自動產生 WebReference 資料夾與檔案——這些就是代理。客戶端呼叫代理即可解決遠端訪問的問題。
2. 虛擬代理(Virtual Proxy)#
根據需要建立開銷很大的物件,透過它來存放實例化需要很長時間的真實物件。[DP]
例如打開一個有大量文字與圖片的 HTML 網頁:
- 文字立即顯示
- 圖片框先用虛擬代理替代,逐張下載完成後再呈現
3. 安全代理(Protection Proxy)#
用來控制真實物件訪問時的權限[DP]。一般用於物件應有不同訪問權限的情境。
4. 智慧引用(Smart Reference)#
當呼叫真實物件時,代理處理一些額外事務[DP]:
- 計算真實物件的引用次數,當沒有引用時自動釋放
- 第一次引用持久物件時將它載入記憶體
- 訪問實際物件前檢查是否已被鎖定
本章小結#
代理模式其實就是在訪問物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
說白了,代理就是真實物件的代表。