引子:替朋友代送禮物#

戴勵高中時喜歡同班的嬌嬌,但他不直接表白,而是說「我幫好朋友卓賈易代送禮物」——芭比娃娃、鮮花、巧克力,全都假借「卓賈易」的名義送出。最後嬌嬌反過來告白:「我願意和做朋友!」。事實上,根本沒有卓賈易這個人。

戴勵就是「卓賈易」的代理人——這正是代理模式的生活對應。

第一版:沒有代理的程式#

讓「追求者」直接送禮給「被追求者」:

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 ..> Subject
abstract 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]:

  • 計算真實物件的引用次數,當沒有引用時自動釋放
  • 第一次引用持久物件時將它載入記憶體
  • 訪問實際物件前檢查是否已被鎖定

本章小結#

代理模式其實就是在訪問物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。

說白了,代理就是真實物件的代表。