設計模式的征途—13.代理(Proxy)模式
所謂代購,簡單說來就是找人幫忙購買所需要的商品。代購分為兩種類型,一種是因為在當地買不到某件商品,又或者是因為當地這件商品的價格比其他地區的貴,因此托人在其他地區甚至國外購買該商品,然后通過快遞發貨或直接攜帶回來。另一種則是消費者對想要購買的商品相關信息的缺乏,自己無法確定其實際價值,因此只好委托中介講價或購買。在軟件開發中,有一種設計模式可以提供與代購類似的功能,由于某些原因,客戶端不想或者不能直接訪問某個對象,此時可以通過一個稱之為“代理”的第三者來實現間接訪問,該方案對應的設計模式則被稱為代理模式。
| 代理模式(Proxy) | 學習難度:★★★☆☆ | 使用頻率:★★★★☆ |
一、收費商務查詢系統的設計
M公司承接了某信息咨詢公司的收費商務信息查詢系統的開發任務,該系統的基本需求如下:
(1)在進行商務信息查詢之前用戶需要通過身份驗證,只有合法用戶才能夠使用該查詢系統。
(2)在進行商務信息查詢時,系統需要記錄查詢日志,以便根據查詢次數收取查詢費用。
M公司開發人員已經完成了商務信息查詢模塊的開發任務,他們希望能夠以一種松耦合的方式向原有系統增加身份驗證和日志記錄功能,客戶端代碼可以無區別地對待原始的商務信息查詢模塊和增加新功能之后的商務信息查詢模塊,而且可能在將來還要在該信息查詢模塊中增加一些新的功能。
M公司開發人員通過分析,決定采用一種間接訪問的方式來實現該商務信息查詢系統的設計,在客戶端對象和信息查詢對象之間增加一個代理對象,讓代理對象來實現驗證和日志記錄功能,而無須直接對原有的商務信息查詢對象進行修改,如下圖所示:

這種設計方案即為代理模式,它為對象的訪問提供了一種設計方案,而且具有多種不同的類型,應用相當廣泛。
二、代理模式概述
2.1 代理模式簡介
代理(Proxy)模式:給某一個對象提供一個代理,并由代理對象控制對原對象的引用。代理模式是一種對象結構型模式。
可以看重,代理模式的重點就在于引入了一個新的代理對象,代理對象可以在客戶端對象和目標對象之間起到中介的作用,去掉客戶不能看到的內容和服務或者添加客戶需要的額外服務。
2.2 代理模式結構

代理模式主要包含以下3個角色:
(1)Subject(抽象主題角色):聲明真實主題和代理主題的共同接口,使得在任何使用真實主題的地方都可以使用代理主題。
(2)Proxy(代理主題角色):代理主題角色內部包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;
(3)RealSubject(真實主題角色):定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作。
三、實現收費商務查詢系統
3.1 系統設計結構

3.2 具體代碼實現
(1)抽象主題 => ISearcher接口
/// <summary> /// 抽象主題類:抽象查詢接口 /// </summary> public interface ISearcher { string DoSearch(string userID, string keyword); }
(2)真實主題 => RealSearcher類
/// <summary> /// 真是主題類:具體查詢器 /// </summary> public class RealSearcher { /// <summary> /// 模擬查詢商務信息 /// </summary> /// <returns></returns> public string DoSearch(string userID, string keyword) { Console.WriteLine("{0} 使用關鍵詞 {1}", userID, keyword); return "返回具體內容"; } }
此外,還有兩個業務類:AccessValidator用于驗證用戶身份,Logger則用于記錄日志。
/// <summary> /// 業務類:身份驗證類 /// </summary> public class AccessValidator { /// <summary> /// 模擬實現登錄驗證 /// </summary> /// <param name="userID"></param> /// <returns></returns> public bool Validate(string userID) { Console.WriteLine("在數據庫中驗證用戶 {0} 是否是合法用戶?", userID); if (userID.Equals("楊過", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("{0} 登錄成功!", userID); return true; } else { Console.WriteLine("{0} 登錄失敗!", userID); return false; } } } /// <summary> /// 業務類:日志記錄類 /// </summary> public class Logger { /// <summary> /// 模擬實現日志記錄 /// </summary> /// <param name="userID"></param> public void Log(string userID) { Console.WriteLine("更新數據庫,用戶 {0} 查詢次數加1!", userID); } }
(3)代理主題 => ProxySearcher類
/// <summary> /// 代理主題類:代理查詢 /// </summary> public class ProxySearcher : ISearcher { private RealSearcher searcher = new RealSearcher(); // 維持一個對真實主題的引用 private AccessValidator validator; private Logger logger; public string DoSearch(string userID, string keyword) { if (Validate(userID)) { string result = searcher.DoSearch(userID, keyword); this.Log(userID); return result; } return null; } /// <summary> /// 創建訪問驗證對象并調用其Validate()方法進行身份驗證 /// </summary> /// <returns></returns> public bool Validate(string userID) { validator = new AccessValidator(); return validator.Validate(userID); } /// <summary> /// 創建日志記錄器并調用Log()方法實現日志記錄 /// </summary> /// <param name="userID"></param> public void Log(string userID) { logger = new Logger(); logger.Log(userID); } }
(4)客戶端調用
① 為了提高系統可擴展性,這里將代理主題類存在了配置文件中
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!-- Proxy Setting --> <add key="ProxyName" value="Manulife.ChengDu.DesignPattern.Proxy.ProxySearcher, Manulife.ChengDu.DesignPattern.Proxy" /> </appSettings> </configuration>
② 客戶端調試代碼
public class Program { public static void Main(string[] args) { ISearcher searcher = AppConfigHelper.GetProxyInstance() as ISearcher; if (searcher != null) { string result = searcher.DoSearch("楊過", "玉女心經"); } Console.ReadKey(); } }
這里AppConfigHelper主要用于訪問配置文件并通過反射生成實例對象
public class AppConfigHelper { public static string GetProxyName() { string factoryName = null; try { factoryName = System.Configuration.ConfigurationManager.AppSettings["ProxyName"]; } catch (Exception ex) { Console.WriteLine(ex.Message); } return factoryName; } public static object GetProxyInstance() { string assemblyName = AppConfigHelper.GetProxyName(); Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type); return instance; } }
③ 運行結果

四、代理模式總結
4.1 主要優點
(1)協調了調用者和被調用者,一定程度上降低了系統的耦合度 => 符合迪米特法則
(2)客戶端針對抽象主題角色編程,增加和更換代理類無須修改源代碼 => 符合開閉原則
4.2 應用場景
(1)客戶端需要訪問遠程主機中的對象時 => 遠程代理
(2)需要一個消耗資源較少的對象來代表一個消耗資源較多的對象 => 降低系統開銷
(3)需要控制對一個對象的訪問,為不同用戶提供不同級別的訪問權限 => 保護代理
參考資料

劉偉,《設計模式的藝術—軟件開發人員內功修煉之道》

所謂代購,簡單說來就是找人幫忙購買所需要的商品。代購分為兩種類型,一種是因為在當地買不到某件商品,又或者是因為當地這件商品的價格比其他地區的貴,因此托人在其他地區甚至國外購買該商品,然后通過快遞發貨或直接攜帶回來。另一種則是消費者對想要購買的商品相關信息的缺乏,自己無法確定其實際價值,因此只好委托中介講價或購買。在軟件開發中,有一種設計模式可以提供與代購類似的功能,由于某些原因,客戶端不想或者不能直接訪問某個對象,此時可以通過一個稱之為“代理”的第三者來實現間接訪問,該方案對應的設計模式則被稱為代理模式。

浙公網安備 33010602011771號