1. 程式人生 > >設計模式的征途—11.外觀(Facade)模式

設計模式的征途—11.外觀(Facade)模式

在軟體開發中,有時候為了完成一項較為複雜的功能,一個類需要和多個其他業務類互動,而這些需要互動的業務類經常會作為一個完整的整體出現,由於涉及的類比較多,導致使用時程式碼較為複雜,此時,特別需要一個類似服務員一樣的角色,由他來負責和多個業務類進行互動,而使用這些業務類的類只需要和該類進行互動即可。外觀模式通過引入一個新的外觀類來實現該功能,外觀類充當了軟體系統中的“服務員”,它為多個業務類的呼叫提供了一個統一的入口,簡化了類與類之間的互動。

外觀模式(Facade) 學習難度:★☆☆☆☆ 使用頻率:★★★★★

一、檔案加密模組設計

1.1 需求背景

M公司想要開發一個用於多個軟體的檔案加密模組,該模組可以對檔案中的資料進行加密並將加密後的資料儲存在一個新檔案中,具體的流程包括3個部分,分別是讀取原始檔、加密、儲存加密之後的檔案。其中,讀取檔案和儲存檔案使用流來實現,加密操作通過求模運算實現。這3個操作相對獨立,為了實現程式碼地獨立重用,讓設計更加符合單一職責原則,這3個操作的業務程式碼封裝在3個不同的類中。

1.2 初始設計

  M公司開發人員獨立實現了這3個具體業務類:FileReader用於讀取檔案,CipherMachine用於對資料加密,FileWriter用於儲存檔案。由於該檔案加密模組的通用性,它在M公司開發的多款軟體中都得以使用,包括財務管理軟體、公文審批系統、郵件管理系統等,如下圖所示:

  從上圖中不難發現,在每一次使用這3個類時都需要編寫程式碼與它們逐個進行互動,客戶端程式碼如下:

    public static void Main()
    {
        FileReader reader = new FileReader();                       //
檔案讀取類 CipherMachine cipher = new CipherMachine(); // 資料加密類 FileWriter writer = new FileWriter(); // 檔案儲存類 reader = new FileReader(); cipher = new CipherMachine(); writer = new FileWriter(); string plainStr = reader.Read("
Facade/src.txt"); // 讀取原始檔 string encryptStr = cipher.Encrypt(plainStr); // 加密 writer.Write(encryptStr, "Facade/des.txt"); // 將加密結果寫入新檔案 }

  經過分析後發現,該方案雖然能夠實現預期功能,但存在以下2個問題:

  (1)FileReader、CipherMachie與FileWriter類經常作為一個整體同時出現,但是如果按照上述方案進行設計和實現,在每一次使用這3個類時,客戶端程式碼都需要與它們逐個進行互動,導致客戶端程式碼較為複雜,且在每次使用它們時很多程式碼都會重複出現。

  (2)如果需要更換一個加密類,例如將CipherMachine改為NewCipherMachine,則所有使用該檔案加密模組的程式碼都需要進行修改,系統維護難度增大,靈活性和可擴充套件性較差。

二、外觀模式概述

2.1 外觀模式簡介

  根據單一職責原則,在軟體中將一個系統劃分為若干個子系統有利於降低整個系統的複雜性,一個常見的設計目標就是使客戶類與子系統之間的通訊和相互依賴關係達到最小,而達到該目標的途徑之一就是引入一個外觀(Facade)角色,它為子系統的訪問提供了一個簡單而單一的入口。

外觀(Facade)模式:外部與一個子系統的通訊通過一個統一的外觀角色進行,為子系統中的一組介面提供一個一致的入口,外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。  

2.2 外觀模式結構與角色

  外觀模式沒有一個一般化的類圖描述,通常使用示意圖來表示外觀模式,如下圖所示:

  當然,下圖所示的類圖也可以作為外觀模式的結構型描述形式之一。

  外觀模式主要包含兩個角色:

  (1)Facade(外觀角色):在客戶端可以呼叫這個角色的方法,在外觀角色中可以知道相關的子系統的功能和責任;在正常情況下,它將所有從客戶端發來的請求委派到相應的子系統中去,傳遞給相應的子系統物件處理。

  (2)SubSystem(子系統角色):在軟體系統中可以有一個或者多個子系統角色,每一個子系統可以不是一個單獨的類,而是一個類的集合,它實現子系統的功能;子系統並不知道外觀(又稱為門面)的存在,對於子系統而言,外觀角色僅僅是另一個客戶端而已。

三、重構檔案加密模組

3.1 重構後的設計結構

  為了降低系統耦合度,封裝與多個子系統進行互動的程式碼,M公司開發人員使用外觀模式來重構檔案加密模組的設計,重構後的結構如下圖所示:

3.2 重構後的程式碼實現

  (1)子系統類:FileReader、CipherMachie和FileWriter

    /// <summary>
    /// 檔案讀取類:子系統A
    /// </summary>
    public class FileReader
    {
        public string Read(string fileNameSrc)
        {
            Console.WriteLine("讀取檔案,獲取明文:");
            string result = string.Empty;
            using (System.IO.FileStream fsRead = new System.IO.FileStream(fileNameSrc, System.IO.FileMode.Open))
            {
                int fsLen = (int)fsRead.Length;
                byte[] heByte = new byte[fsLen];
                int r = fsRead.Read(heByte, 0, heByte.Length);
                result = System.Text.Encoding.UTF8.GetString(heByte);
            }

            return result;
        }
    }

    /// <summary>
    /// 資料加密類:子系統B
    /// </summary>
    public class CipherMachine
    {
        public string Encrypt(string plainText)
        {
            Console.WriteLine("資料加密,將明文轉換為密文:");
            StringBuilder result = new StringBuilder();

            for (int i = 0; i < plainText.Length; i++)
            {
                string ch = Convert.ToString(plainText[i] % 7);
                result.Append(ch);
            }

            string encryptedResult = result.ToString();
            Console.WriteLine(encryptedResult);
            return encryptedResult;
        }
    }

    /// <summary>
    /// 檔案儲存類:子系統C
    /// </summary>
    public class FileWriter
    {
        public void Write(string encryptedStr, string fileNameDes)
        {
            Console.WriteLine("儲存密文,寫入檔案:");
            byte[] myByte = System.Text.Encoding.UTF8.GetBytes(encryptedStr);
            using (System.IO.FileStream fsWrite = new System.IO.FileStream(fileNameDes, System.IO.FileMode.Append))
            {
                fsWrite.Write(myByte, 0, myByte.Length);
            };

            Console.WriteLine("寫入檔案成功:100%");
        }
    }

  (2)外觀類:EncrytFacade

    public class EncryptFacade
    {
        private FileReader reader;
        private CipherMachine cipher;
        private FileWriter writer;

        public EncryptFacade()
        {
            reader = new FileReader();
            cipher = new CipherMachine();
            writer = new FileWriter();
        }

        public void FileEncrypt(string fileNameSrc, string fileNameDes)
        {
            string plainStr = reader.Read(fileNameSrc);
            string encryptedStr = cipher.Encrypt(plainStr);
            writer.Write(encryptedStr, fileNameDes);
        }
    }

  (3)客戶端呼叫:

    public class Program
    {
        public static void Main(string[] args)
        {
            EncryptFacade facade = new EncryptFacade();
            facade.FileEncrypt("Facade/src.txt", "Facade/des.txt");

            Console.ReadKey();
        }
    }

  這裡,src.txt的內容就是一句:Hello World!

  最終執行結果如下圖所示:

  

四、二次重構檔案加密模組

4.1 新的加密類

  在標準的外觀模式實現中,如果需要增加、刪除或更換與外觀類互動的子系統類,勢必會修改外觀類或客戶端的原始碼,這就將違背開閉原則。因此,我們可以引入抽象外觀類來對系統進行重構,可以在一定程度上解決該問題。

  假設在M公司開發的檔案加密模組中需要更換一個加密類,不再使用原有的基於求模運算的加密類CipherMachine,而改為基於移位運算的新加密類NewCipherMachine,其中NewCipherMachine類的程式碼如下:

    /// <summary>
    /// 新資料加密類:子系統B
    /// </summary>
    public class NewCipherMachine
    {
        public string Encrypt(string plainText)
        {
            Console.WriteLine("資料加密,將明文轉換為密文:");
            StringBuilder result = new StringBuilder();
            int key = 10; // 設定金鑰,移位數為10

            for (int i = 0; i < plainText.Length; i++)
            {
                char c = plainText[i];
                // 小寫字母位移
                if (c >= 'a' && c <= 'z')
                {
                    c += Convert.ToChar(key % 26);
                    if (c > 'z')
                    {
                        c -= Convert.ToChar(26);
                    }

                    if (c < 'a')
                    {
                        c += Convert.ToChar(26);
                    }
                }

                // 大寫字母位移
                if (c >= 'A' && c <= 'Z')
                {
                    c += Convert.ToChar(key % 26);
                    if (c > 'Z')
                    {
                        c -= Convert.ToChar(26);
                    }

                    if (c < 'A')
                    {
                        c += Convert.ToChar(26);
                    }
                }
                result.Append(c);
            }

            string encryptedResult = result.ToString();
            Console.WriteLine(encryptedResult);
            return encryptedResult;
        }
    }

4.2 重構後的設計

  如何在不修改原始碼的基礎之上使用新的外觀類呢?解決辦法是:引入一個新的抽象外觀類,客戶端只針對抽象程式設計,而在執行時再確定具體外觀類。引入抽象外觀類之後的設計結構圖如下圖所示:

  在新的設計中,客戶端只針對抽象外觀類AbstractEncryptFacade進行程式設計。

4.3 程式碼實現 

  (1)抽象外觀類:AbstractEncryptFacade

    /// <summary>
    /// 抽象外觀類
    /// </summary>
    public abstract class AbstractEncryptFacade
    {
        public abstract void FileEncrypt(string fileNameSrc, string fileNameDes);
    }

  (2)新的外觀類:NewEncryptFacade

    public class NewEncryptFacade : AbstractEncryptFacade
    {
        private FileReader reader;
        private NewCipherMachine cipher;
        private FileWriter writer;

        public NewEncryptFacade()
        {
            reader = new FileReader();
            cipher = new NewCipherMachine();
            writer = new FileWriter();
        }

        public override void FileEncrypt(string fileNameSrc, string fileNameDes)
        {
            string plainStr = reader.Read(fileNameSrc);
            string encryptedStr = cipher.Encrypt(plainStr);
            writer.Write(encryptedStr, fileNameDes);
        }
    }

  (3)配置檔案將具體外觀類進行配置:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- EncryptFacade Setting -->
    <add key="EncryptFacadeName" value="Manulife.ChengDu.DesignPattern.Facade.NewEncryptFacade, Manulife.ChengDu.DesignPattern.Facade" />
  </appSettings>
</configuration>

  (4)客戶端呼叫

    public class Program
    {
        public static void Main(string[] args)
        {
            AbstractEncryptFacade newFacade = AppConfigHelper.GetFacadeInstance() as AbstractEncryptFacade;
            if (newFacade != null)
            {
                newFacade.FileEncrypt("Facade/src.txt", "Facade/des.txt");
            }

            Console.ReadKey();
        }
    }

  其中,AppConfigHelper用於讀取配置檔案的配置並藉助反射動態生成具體外觀類例項,其程式碼如下:

    public class AppConfigHelper
    {
        public static string GetFacadeName()
        {
            string factoryName = null;
            try
            {
                factoryName = System.Configuration.ConfigurationManager.AppSettings["EncryptFacadeName"];
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return factoryName;
        }

        public static object GetFacadeInstance()
        {
            string assemblyName = AppConfigHelper.GetFacadeName();
            Type type = Type.GetType(assemblyName);

            var instance = Activator.CreateInstance(type);
            return instance;
        }
    }
View Code

  最後,執行結果如下圖所示:

  

  此時,如果需要再次修改具體外觀類,只需要新增一個外觀類,並修改配置檔案即可,原有程式碼無須再次修改,符合開閉原則。

五、外觀模式小結

5.1 主要優點

  (1)對客戶端遮蔽了子系統元件,減少了客戶端需要處理的物件數量並且使得子系統使用起來更加容易。

  (2)實現了子系統與客戶端之間鬆耦合。

  (3)提供了一個訪問子系統的統一入口,並不影響客戶端直接使用子系統。

5.2 應用場景

  (1)想要為訪問一系列複雜的子系統提供一個統一的簡單入口 => 使用外觀模式吧!

  (2)客戶端與多個子系統之間存在很大的依賴性,引入外觀類可以將子系統和客戶端解耦

  (3)在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯絡 => 通過外觀類建立聯絡,降低層與層之間的耦合度!

參考資料

  DesignPattern

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

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

設計模式征途11.外觀Facade模式

在軟體開發中,有時候為了完成一項較為複雜的功能,一個類需要和多個其他業務類互動,而這些需要互動的業務類經常會作為一個完整的整體出現,由於涉及的類比較多,導致使用時程式碼較為複雜,此時,特別需要一個類似服務員一樣的角色,由他來負責和多個業務類進行互動,而使用這些業務類的類只需要和該類進行互動即可。外觀模式通過引

設計模式外觀Facade模式

text 一起 其它 QQ 如何 互調 IT 抽象 整合 設計模式:外觀(Facade)模式 一、前言 外觀模式是一種非常簡單的模式,簡單到我們經常都會使用,比如對於類A和B,如果兩者需要交互,經過一定的處理過程才能實現某一個具體的功能,那麽我們可以將這個處理的過程定義

設計模式外觀facade模式

一、概念介紹   外觀模式(Facade),他隱藏了系統的複雜性,並向客戶端提供了一個可以訪問系統的介面。這種型別的設計模式屬於結構性模式。為子系統中的一組介面提供了一個統一的訪問介面,這個介面使得子系統更容易被訪問或者使用。  二、角色及使用場景   簡單來說,該

外觀Facade模式

什麼是(Facade)模式?     Facade(外觀)模式為子系統中的各類(或結構與方法)提供一個簡明一致的介面,隱藏子系統的複雜性,使子系統更加容易使用(出自百度文庫)。即當子系統複雜或者繁鎖時,我們讓子系統提供一個視窗,程式中稱為介面,其它程式或者物件就通過這個

設計模式6—— 結構型 ——外觀Facade

簡介 定義:又叫門面模式,提供一個統一的介面,用來訪問子系統中的一群介面。 解釋:外觀模式定義了一個高層介面,讓子系統更容易被使用。 型別:結構型 適用場景: 子系統越來越複雜,外觀模式能夠提供簡單的呼叫介面。

設計模式征途—5.原型Prototype模式

pla width 共享 太多的 isp text 一模一樣 軟件 集合 相信大多數的人都看過《西遊記》,對孫悟空拔毛變出小猴子的故事情節應該都很熟悉。孫悟空可以用猴毛根據自己的形象復制出很多跟自己一模一樣的小猴兵出來,其實在設計模式中也有一個類似的模式,我們可以通過一個原

設計模式征途—16.訪問者Visitor模式

lose mar rtm image 3.1 conf 系統 .get 封裝性 在患者就醫時,醫生會根據病情開具處方單,很多醫院都會存在以下這個流程:劃價人員拿到處方單之後根據藥品名稱和數量計算總價,而藥房工作人員根據藥品名稱和數量準備藥品,如下圖所示。 在軟件開發中

設計模式征途—18.策略Strategy模式

滿足 應用 基礎 blog title pla 生成 display 多個 俗話說條條大路通羅馬,很多情況下實現某個目標地途徑都不只一條。在軟件開發中,也會時常遇到這樣的情況,實現某一個功能有多條途徑,每一條途徑都對應一種算法。此時,可以使用一種設計模式來實現靈活地選擇解決

設計模式征途—20.備忘錄Memento模式

行為 修煉之道 mda 3.2 ima 位置 pri 捕獲 spl 相信每個人都有後悔的時候,但是人生並無後悔藥,有些錯誤一旦發生就無法再挽回,有些事一旦錯過就不會再重來,有些話一旦說出口也就不可能再收回,這就是人生。為了不讓自己後悔,我們總是需要三思而後行。這裏我們要學習

設計模式-外觀門面模式

文章目錄 外觀模式介紹 使用場景 程式碼例項 總結 外觀模式介紹 外觀模式,也成為門面模式,它隱藏了系統的複雜性,並向客戶端提供了一個可以訪問系統的介面。 適用方要完成一個功能,需要呼叫提供方的多個介面、方法,呼叫過程複雜時,

“戲”說設計模式——外觀門面模式

首先請出今天的主人公——“黑旋風”李逵 李逵:“我是不是萌萌噠?”  一部《水滸傳》說盡了一群英雄好漢的悲歡離合!經典著作的水滸,含蓄地概括了中國各個朝代的變遷和演變,客觀地揭露了頑固的中國倫理道德,以及那些倫理道德對國人的愚弄和危害。 故事劇情 宋江酒後在潯陽樓

設計模式征途—10.裝飾Decorator模式

雖然目前房價依舊很高,就連我所在的成都郊區(非中心城區)的房價均價都早已破萬,但卻還是阻擋不了大家對新房的渴望和買房的熱情。如果大家買的是清水房,那麼無疑還有一項艱鉅的任務在等著大家,那就是裝修。對新房的裝修並沒有改變房屋用於居住的本質,但它可以讓房子變得更加漂亮和溫馨以及更加實用。在軟體設計中,也有一種類似

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

所謂代購,簡單說來就是找人幫忙購買所需要的商品。代購分為兩種型別,一種是因為在當地買不到某件商品,又或者是因為當地這件商品的價格比其他地區的貴,因此託人在其他地區甚至國外購買該商品,然後通過快遞發貨或直接攜帶回來。另一種則是消費者對想要購買的商品相關資訊的缺乏,自己無法確定其實際價值,因此只好委託中介講價或購

設計模式征途—19.命令Command模式

在生活中,我們裝修新房的最後幾道工序之一是安裝插座和開關,通過開關可以控制一些電器的開啟和關閉,例如電燈或換氣扇。在購買開關時,使用者並不知道它將來到底用於控制什麼電器,也就是說,開關與電燈、換氣扇並無直接關係,一個開關在安裝之後可能用來控制電燈,也可能用來控制換氣扇或者其他電器裝置。相同的開關可以通過不同的

設計模式征途—23.直譯器Interpreter模式

雖然目前計算機程式語言有好幾百種,但有時人們還是希望用一些簡單的語言來實現特定的操作,只需要向計算機輸入一個句子或檔案,就能按照預定的文法規則來對句子或檔案進行解釋。例如,我們想要只輸入一個加法/減法表示式,它就能夠計算出表示式結果。例如輸入“1+2+3-4+1”時,將輸出計算結果為3。像C++,Java或C

設計模式征途—7.介面卡Adapter模式

在現實生活中,我們的膝上型電腦的工作電壓大多數都是20V,而我國的家庭用電是220V,如何讓20V的膝上型電腦能夠工作在220V的電壓下工作?答案:引入一個電源介面卡,俗稱變壓器,有了這個電源介面卡,生活用電和膝上型電腦即可相容。 在軟體開發中,有時候也會存在這種不相容的情況,我們也可以像電源介面卡一樣引入

設計模式:裝飾器Decorator模式

讓我 分享圖片 底部 .com 一件事 輸出 PE 新的 int 設計模式:裝飾器(Decorator)模式 一、前言 裝飾器模式也是一種非常重要的模式,在Java以及程序設計中占據著重要的地位。比如Java的數據流處理,我們可能看到數據流經過不同的類的包裝和包裹,最

設計模式:觀察者Observer模式

image 強制轉換 trace vat PE sta obs observer -a 設計模式:觀察者(Observer)模式 一、前言 觀察者模式其實最好的名稱應該是“發布訂閱”模式,和我們現在大數據之中的發布訂閱方式比較類似,但是也有區別的地方,在上一個設計模式,

設計模式:享元FlyWeight模式

例子 清理 什麽 public == lean http 變量 -- 設計模式:享元(FlyWeight)模式 一、前言 享元(FlyWeight)模式顧名思義,既是輕量級的,原因就是享元,共享元素,這裏的元素指的是對象。如何共享對象,那就是在檢測對象產生的時候,如

門面Facade模式

外部與一個子系統的通訊必須通過一個統一的門面(Facade)物件進行,這就是門面模式。 醫院的例子 用一個例子進行說明,如果把醫院作為一個子系統,按照部門職能,這個系統可以劃分為掛號、門診、劃價、化驗、收費、取藥等。看病的病人要與這些部門打交道,就如同一個子系統的客戶端與一個子系統的各個類打交道一樣,