意圖(Intent)#

為另一個物件提供代理或佔位符(placeholder),以控制對該物件的存取。

別名(Also Known As)#

Surrogate

動機(Motivation)#

控制物件存取的原因之一,是延遲建立和初始化的完整成本,直到真正需要使用時才付出。考慮一個文件編輯器可以在文件中嵌入圖形物件。大型的點陣圖影像建立成本高昂,但開啟文件應該要快速,而且並非所有影像同時可見,不需要一次建立所有昂貴物件。

解決方案是使用一個 image proxy 來代替真正的影像。Proxy 的行為就像影像一樣,在真正需要顯示時才建立實際影像。Proxy 儲存影像的檔名作為引用,也儲存影像的尺寸(extent),使得排版器可以在不實例化影像的情況下取得尺寸資訊。

Proxy 的核心原則:在不改變介面的前提下控制對物件的存取。Proxy 提供與真實物件相同的介面,客戶端無法區分它們,但 Proxy 可以在存取真實物件之前或之後執行額外的控制邏輯。

適用場景(Applicability)#

Proxy 適用於需要比簡單指標更通用或精密的物件引用時。常見的幾種類型:

類型說明
Remote Proxy為不同位址空間中的物件提供本地代表。NEXTSTEP 的 NXProxy 類別即為此用途
Virtual Proxy依需求建立昂貴的物件。動機中的 ImageProxy 就是例子
Protection Proxy控制對原始物件的存取權限。適用於物件需要不同存取權限的場景
Smart Reference取代裸指標,在物件被存取時執行額外動作:Reference counting,在沒有引用時自動釋放(smart pointer);首次引用時將持久化物件載入記憶體;存取前檢查物件是否已被鎖定

結構(Structure)#

Proxy 持有對 RealSubject 的引用(或可在需要時建立),提供與 Subject 相同的介面,讓 Proxy 可以替代 RealSubject。客戶端透過 Subject 介面操作,不知道是否在使用 Proxy。

classDiagram
    class Subject {
        <<interface>>
        +Request()
    }
    class RealSubject {
        +Request()
    }
    class Proxy {
        -realSubject
        +Request()
    }
    Subject <|.. RealSubject
    Subject <|.. Proxy
    Proxy o--> RealSubject
    Client --> Subject

參與者(Participants)#

參與者範例職責
ProxyImageProxy持有引用以存取真實主題(若 RealSubject 和 Subject 介面相同,可直接引用 Subject);提供與 Subject 相同的介面,使 Proxy 可以替代真實主題;控制對真實主題的存取,可能負責建立和刪除它;不同種類的 Proxy 有不同的額外職責:Remote proxy 負責編碼請求並發送到遠端主題,Virtual proxy 可快取真實主題的額外資訊以延遲存取,Protection proxy 檢查呼叫者是否有權限執行請求
SubjectGraphic定義 RealSubject 和 Proxy 的共同介面
RealSubjectImageProxy 所代表的真實物件

協作方式(Collaborations)#

  • Proxy 在適當時機將請求轉發給 RealSubject,具體行為取決於 Proxy 的種類

優缺點(Consequences)#

Proxy 模式在存取物件時引入一層間接層,這層間接層有多種用途:

  • Remote proxy 隱藏物件位於不同位址空間的事實
  • Virtual proxy 可以執行最佳化,例如依需求建立物件
  • Protection proxysmart reference 允許在物件被存取時執行額外的管理工作

另一項 Proxy 可隱藏的最佳化是 copy-on-write:複製大型複雜物件成本很高,若副本從未被修改,就不需要真正複製。Proxy 延遲複製操作,只在物件被修改時才真正複製。實作需要搭配 reference counting——複製 proxy 只增加引用計數,修改時才真正複製並減少原物件的引用計數。

Copy-on-write 是 Virtual Proxy 的一種特殊應用。對於需要頻繁複製但很少修改的重量級物件,這種技巧可以大幅降低複製成本。

實作要點(Implementation)#

  • C++ 成員存取運算子重載:重載 operator-> 可以在物件被解引用時執行額外工作,適合實作某些 proxy(如自動載入)。但此方法無法區分不同的操作——若需要根據呼叫的具體操作決定行為(如 virtual proxy 只在 Draw 被呼叫時載入),就必須手動實作每個轉發操作
  • Smalltalk 的 doesNotUnderstand::可攔截所有未定義的訊息並轉發給真實主題,實現通用的 proxy。也可用來實作 protection proxy——檢查訊息是否在允許清單中
  • Proxy 不一定要知道 RealSubject 的具體型別:若 Proxy 可以僅透過抽象介面操作主題,就不需要為每個 RealSubject 類別建立對應的 Proxy 類別
  • 如何在實例化前引用主題:某些 proxy 需要在主題存在於磁碟或記憶體之前就能引用它,需要某種位址空間無關的物件識別符(如檔案名稱)

已知應用(Known Uses)#

  • ET++:文字建構區塊類別中的 virtual proxy
  • NEXTSTEP:NXProxy 作為遠端物件的本地代表,負責編碼/解碼訊息及其參數
  • Smalltalk:McCullough 討論了存取遠端物件的 proxy;Pascoe 描述了用「Encapsulator」提供方法呼叫的副作用和存取控制

相關模式(Related Patterns)#

  • Adapter:Adapter 提供不同的介面;Proxy 提供與主題相同的介面(但 protection proxy 可能拒絕某些操作,使有效介面成為主題的子集)
  • Decorator:Decorator 為物件增加職責;Proxy 控制對物件的存取。兩者的實作可能相似——protection proxy 的實作可能與 Decorator 完全一樣。但 remote proxy 不包含對真實主題的直接引用(而是 host ID 和本地位址),virtual proxy 一開始只有間接引用(如檔案名稱),最終才取得並使用直接引用