C# Design Patterns (4) - Proxy
本帖介紹 Proxy Pattern (代理模式)。
Proxy Pattern (代理模式)
The Proxy Pattern provides a surrogate or placeholder for another object to control access to it...
- Design Patterns: Elements of Reusable Object-Oriented Software
在 GoF 的書中,對 Proxy 模式的定義為:替某個對象,提供一個替身,以控制外界對這個對象的訪問。而這個被替身代理的對象 (被代理者),可能是遠端的對象、創建時需要高成本或大計算量的對象,或需要安全控制的對象。
圖 1 此圖為 Proxy 模式的經典類圖

上方圖 1 的 Class Diagram,以及「Shell (殼)」示例中,我們有一個 Subject 抽像類,這是 RealSubject 和 Proxy 共同的接口,好讓任何用戶都可將 Proxy 對象 (代理者) 視為 RealSubject 對象 (被代理者,亦即真實的對象) 來處理。
其中 RealSubject 是真正做事的對象,它是被 Proxy 代理的對象,它的方法是真正做事的函數,并會將一些復雜的工作封裝在其方法里,而無須讓客戶端程序知道實現細節為何;而 Proxy 中的同名稱方法 (Request 方法),則可做一些邏輯判斷,比如上例中,我們做了 realSubject 是否為 null 的 if 判斷,亦即只有客戶程序第一次調用此函數時,才去創建 RealSubject 對象。
客戶端和 RealSubject 的互動,都必須透過 Proxy。也由于 Proxy 和 RealSubject 實現了相同的接口,所以客戶在任何需要 RealSubject 的地方,都可以用 Proxy 取而代之。此外,Proxy 也控制了客戶端對 RealSubject 的訪問,其目的如同本帖一開始所說的,因為 RealSubject 可能是網絡上遠端機器上的對象、創建時需要高成本、或需要安全控管及經過認證才能訪問的對象。其原理類似于下圖 2 的 HTTP Proxy Server 情景,Client-side 想要前往 Internet 取得 Web Server 上的信息時,可透過 Proxy Server 幫忙處理;其中 Web Server 就如同 RealSubject (被代理者),而 Proxy Server 就如同 Proxy (代理者)。
圖 2 基于代理的遠程訪問示意圖
Proxy Pattern 依照功能和目的、運行環境的不同,可概略分成以下幾種:
- 遠程代理 (Remote Proxy):替網絡上機器與機器之間的請求 (request),做「發送 / 接收」和編碼、加密…等工作,讓用戶端程序,只要調用這個代理就能做遠端調用,如:Java RMI、Web Service、.NET Remoting、.NET WCF。其中 Web Service 和 WCF,會在引用的客戶端程序中,產生 App_WebReferences 文件夾、一些檔案和代理類,這些檔案即為此種遠程代理。
- 保護代理 (Protection Proxy; Access Proxy):檢查調用者是否有權限,去訪問真實的對象,例如用戶是否有輸入正確的密碼以通過認證。
- 智能引用代理 (Smart Reference Proxy):當對象被調用時,提供一些額外的操作,例如:記錄對象被調用的次數。
- 虛擬代理 (Virtual Proxy):讓一個資源消耗較大的對象,只有在需要時才會真正被創建,或讓真實對象只有在第一次被調用時才創建。
- 其他,例如:Copy-On-Write Proxy、Cache Proxy、Firewall Proxy、Synchronization Proxy...等 [2], [3], [5]。
在 GoF 中所舉的例子是 Virtual Proxy,舉了一個文檔中內嵌圖片的范例 [4], [11]。若圖片是在文檔 (如:PDF、PowerPoint) 的其中某一頁,用戶剛打開文檔時,并不需要載入圖片,可先用一個 ImageProxy,代替真實的圖片被載入;當用戶滾動滾動條、轉到文檔特定的頁數時,才真正從硬盤載入圖片,以求開啟此文檔時能加快速度,讓用戶對此軟件有較好的體驗。
圖 3 虛擬代理 (Virtual Proxy) 在 GoF 示例的類圖,與本帖圖 1 的原理相同
如上圖 3 及下方代碼所示,當文檔被開啟時,ImageProxy 對象會代替 (代理) Image 對象被載入,在用戶還沒轉到圖片所在的頁數時,也就是還沒調用 ImageProxy 的 draw 方法時,圖片并不會被載入,因此可加速文檔的開啟、節省內存的使用;當用戶轉到圖片所在頁數時,ImageProxy 的 draw 方法才會被調用,此時才真正去創建 Image 對象、從硬盤中載入圖片。在此例的 draw 方法里,我們實現了「虛擬代理」,只有在方法「第一次」被調用時,才創建資源消耗大的 Image 對象,以節省內存、控制創建成本昂貴的資源。

代理的主要目的之一,是把復雜性封裝起來,讓客戶端程序在引用上更容易,而不需要顧慮藏在身后這些復雜的邏輯。如上方這個示例,我們可以把「載入圖片、繪制圖片在屏幕上」這些較復雜的 .NET API 引用代碼,都封裝在「被代理者」這個真實 Image 對象的幾個自定義方法里。
此外,我們也可用相同于本示例的邏輯,去實現「保護代理 (Protection Proxy)」的觀念,例如要求用戶必須輸入正確的密碼、先通過認證 (Authentication) 后,才能訪問 RealSubject 對象、調用其方法。相關的范例,有興趣的讀者可自行去「C# 3.0 Design Patterns」這本書籍的 O'Reilly 英文官方網站上 [9],下載第二章的源代碼。
接下來的第三個示例,為「數據訪問代理」的 ASP.NET 例子 [10], [12],用來取得 Northwind 數據庫中 Employees 表的記錄總數。其類圖如下圖 4,和本帖第一、第二個示例的類圖略有不同,它是將 RealSubject、Proxy 合而為一,變成單一個 DbCommandProxy 類;其左側的 DbContext 類只是用來協助解決復雜性的問題,包括取得 Web.config 的數據庫連接字符串、建立數據庫的連接。
這個 DbCommandProxy 類,實現了 .NET 用來執行 SQL 語句的原生 IDbCommand 接口 [6]。我們為了將復雜性問題、具體的數據庫連接方式隔離出來,因此另外提供了一個 DbContext 類,并將這些動作都搬移至 DbContext 類去處理,以另一種設計理念實現了 Proxy Pattern。
圖 4 示例 03_DbProxy / Default.aspx.cs 的類圖

--------------------------------------------------------
Proxy Pattern 適用的情景:
- 對象創建的代價比較高。
- 僅在操作被請求時創建對象。
- 對象需要訪問控制,如: 權限驗證,或訪問的同時去執行檢查或簿記工作。
- 需要訪問遠程站點。
- 被訪問時,需要執行一些邏輯判斷的動作。
Proxy Pattern 的優點:
- 降低對象使用的復雜度。
- 增加對象使用的友好度。
- 提高程序的效率和性能 (如同 HTTP 的 Proxy Server)。
Proxy Pattern 的缺點:
- 和一些 Pattern 一樣,Proxy Pattern 會造成系統設計中,類的數量增加。
Proxy Pattern 的其他特性:
- Proxy Pattern 的結構,類似上一篇帖子的 Decorator Pattern (裝飾模式),但是目的不同。
- Decorator Pattern 替對象加上行為,而 Proxy Pattern 則是控制對象的訪問。
- Proxy Pattern 的關系是在設計階段就確定好了的,是事先知道的;而 Decorator Pattern 卻可以動態地添加。
--------------------------------------------------------
本帖的最后,如前三篇帖子一樣,照例提供一位 Java 大師 - 結城浩,所繪制的 Proxy Pattern 趣味四格漫畫,原地址如下:
Giko 貓談 DP 四格漫畫:
http://www.javaworld.com.tw/jute/post/view?bid=44&id=7749&sty=3&age=0&tpg=1&ppg=1#7749
http://www.hyuki.com/dp/cat_Proxy.html
∧_∧ 敲敲敲 ╱
( ) ∧ ∧ < WCF 算是 Remote Proxy 的一個例子....。
( ) (,,゚Д゚) ╲____________
______ (つ_つ____
| 日∇ ╲|ThinkPad|╲
| ========= ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
____________
╱
| 喔~、是 Proxy Pattern 嗎?
╲ __ __________
|╱
∧_∧ ╱
( ・∀・) ∧ ∧ < 你你是誰? ...有..有什么事嬤你?・・・
( ⊃ ) (゚Д゚;) ╲____________
________(つ_つ____
| 日∇ ╲|ThinkPad|╲
| ========= ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________
╱
| 被情勢所逼,才創建對象的一種 Pattern..恩
╲ __ ________
|╱
∧_∧ ╱
( ・∀・) ∧ ∧ < 恩..那算是 Virtual Proxy 的一種....
( ) (;゚Д゚) ╲____________
_____ (つ_つ____
| 日∇╲|ThinkPad|╲
| ========= ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________
╱
| 項目截止日快到了才開始 coding...恩
╲ __ ________
|╱
∧_∧ ╱
( ・∀・) ∧ ∧ < 我又沒說!!
( ⊃ ) (゚Д゚;) ╲____________
_____(つ_つ____
| 日∇ ╲|ThinkPad|╲
| ========= ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/
∧ ∧ < 這可笑不出來
( ) ╲_____________
∼(___ノ
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -