設計模式的征途—13.代理(Proxy)模式

Edison Chou發表於2017-07-17

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

外觀模式(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;
        }
    }
View Code

  ③ 執行結果

  

四、代理模式總結

4.1 主要優點

  (1)協調了呼叫者和被呼叫者,一定程度上降低了系統的耦合度 => 符合迪米特法則

  (2)客戶端針對抽象主題角色程式設計,增加和更換代理類無須修改原始碼 => 符合開閉原則

4.2 應用場景

  (1)客戶端需要訪問遠端主機中的物件時 => 遠端代理

  (2)需要一個消耗資源較少的物件來代表一個消耗資源較多的物件 => 降低系統開銷

  (3)需要控制對一個物件的訪問,為不同使用者提供不同級別的訪問許可權 => 保護代理

參考資料

  DesignPattern

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

 

相關文章