意圖(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)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Proxy | ImageProxy | 持有引用以存取真實主題(若 RealSubject 和 Subject 介面相同,可直接引用 Subject);提供與 Subject 相同的介面,使 Proxy 可以替代真實主題;控制對真實主題的存取,可能負責建立和刪除它;不同種類的 Proxy 有不同的額外職責:Remote proxy 負責編碼請求並發送到遠端主題,Virtual proxy 可快取真實主題的額外資訊以延遲存取,Protection proxy 檢查呼叫者是否有權限執行請求 |
| Subject | Graphic | 定義 RealSubject 和 Proxy 的共同介面 |
| RealSubject | Image | Proxy 所代表的真實物件 |
協作方式(Collaborations)#
- Proxy 在適當時機將請求轉發給 RealSubject,具體行為取決於 Proxy 的種類
優缺點(Consequences)#
Proxy 模式在存取物件時引入一層間接層,這層間接層有多種用途:
- Remote proxy 隱藏物件位於不同位址空間的事實
- Virtual proxy 可以執行最佳化,例如依需求建立物件
- Protection proxy 和 smart 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 一開始只有間接引用(如檔案名稱),最終才取得並使用直接引用