[設計模式-4] 外觀模式

話三國~外觀模式

五丈原諸葛禳星

諸葛亮六出祁山北伐曹魏,但始終無法解決糧草問題而無功而返。為了避免夜長夢多,諸葛亮必須迫使魏軍決一死戰,但司馬懿老奸巨猾,也深知用兵之道,知道時間站在自己的這一邊,因此始終堅守不出。諸葛亮心生一計,便派遣使者以送禮為名,贈送司馬懿巾幗服裝,嘲諷其不敢出戰,乃一婦人也!豈知司馬懿不但不動怒,還當眾穿起,並問使者這樣穿好不好看?司馬懿的部將皆目露殺氣,看主帥受辱,隨時準備拔劍斬殺來使,唯獨司馬懿非常淡定並閒聊諸葛亮近況,使者據實以告,司馬懿笑道:食少事煩,豈能長久。蜀使回歸後,將情形告知諸葛亮,諸葛亮歎曰:彼深知我心也。

蜀漢主簿楊儀諫曰:我見丞相常常親自處理很多瑣事,我認為這非常不好!上下不可相侵,你身為丞相,只需交辦屬下做事即可,若凡事親力親為,最後將搞死自己而一事無成。孔明泣曰:我並不是不知道這個道理,但受到先帝托孤,我怕其他人不像我如此盡心盡力!眾將聽了皆暗自垂淚,按下不表。

諸葛亮一直是我非常喜歡的歷史人物之一,每次看到這段歷史故事,我都會認為劉備有部下如此,真的是死而無憾。但反過來講,諸葛亮犯了什麼錯?以設計原則來看,我認為諸葛亮凡事親力親為,因此認識太多實作類別,相依性太重,所以一旦諸葛亮這個類別掛了,蜀漢這個系統就瀕臨崩壞的危險。

這樣說可能大家還有點模糊,我們用案例來看就會比較容易了解。首先,我們先定義兩個子系統,分別代表諸葛亮目前需要執行的內政工作和軍事工作,內政包含了徵收賦稅以及土地開墾和防災演練共三個類別;軍事包含了糧草補給以及訓練士兵和調兵遣將也是三個類別,而為了不複雜化,類別的方法我都定義的很簡單,大家可以直接往下看。

  class InternalAffairA
    {
        public void InternalMethoad()
        {
            Console.WriteLine("執行內政:徵稅賦稅");
        }
    }

  class InternalAffairB
    {
        public void InternalMethoad()
        {
            Console.WriteLine("執行內政:土地開墾");
        }
    }

  class InternalAffairC
    {
        public void InternalMethoad()
        {
            Console.WriteLine("執行內政:防災演練");
        }
    }

以上是三個內政子系統的類別,都只有提供一個方法,分別輸出:"徵稅賦稅"和"土地開墾"以及"防災演練"。

class MilitaryAffairA
    {
        public void InternalMethoad()
        {
            Console.WriteLine("執行軍事:糧草補給");
        }
    }

class MilitaryAffairB
    {
        public void InternalMethoad()
        {
            Console.WriteLine("執行軍事:訓練士兵");
        }
    }

class MilitaryAffairC
    {
        public void InternalMethoad()
        {
            Console.WriteLine("執行軍事:調兵遣將");
        }
    }

以上是三個軍事子系統的類別,也都只有提供一個方法,分別輸出:"糧草補給"和"訓練士兵"以及"調兵遣將"。

 class Program
    {
        ///Main相當於諸葛亮的類別
        static void Main(string[] args)
        {
            var IaffairA = new InternalAffairA();
            var IaffairB = new InternalAffairB();
            var IaffairC = new InternalAffairC();
            var MaffairA = new MilitaryAffairA();
            var MaffairB = new MilitaryAffairB();
            var MaffairC = new MilitaryAffairC();

            IaffairA.InternalMethoad();
            IaffairB.InternalMethoad();
            IaffairC.InternalMethoad();
            MaffairA.InternalMethoad();
            MaffairB.InternalMethoad();
            MaffairC.InternalMethoad();

            Console.Read();
        }
    }

主角諸葛亮出現了,大家可以看到,諸葛亮必須認識以上兩個子系統包含的六個類別,而且必須直接依賴實作來呼叫它們的方法,換句話說,只要任何一個類別的呼叫方式有異動,就代表主程式(諸葛亮)的類別需要做更動。也難怪諸葛亮會累死了。那要如何改善這樣的設計呢?其實內政和軍事,是兩個不著邊的系統,否則吳國太也不會說,內事不決問張昭;外事不決問周瑜。而我們的主系統要跟子系統互動,卻相依子系統的個別功能上,就顯得太隅和了。這裏要跟大家介紹的外觀設計模式將會是一個很好的解決方案。

定義 

外觀模式:可以定義一個高層的介面,來負責管理與子系統相依的瑣事,讓子系統更加適合使用。

回到剛剛的故事,諸葛亮要如何實做這個模式呢?其實,他真的可以不用每件事都自己管,但事情還是要做呀,一個很好的辦法就是委派政務官,蜀漢在這個時期,其實人才還是很多的,比如在軍事方面,蜀漢的魏延便是頭號猛將;在內政方面,蔣婉也是一個不可多得的人才。轉換成程式語言,便是我們要宣告一個政務官的類別,並提供對應的介面方法(蜀漢人才)來幫助操作子系統(內政和軍事子系統),可參考下面的程式碼。

   /// <summary>
    /// 委派政務官
    /// </summary>
    class FacadeManagers
    {
        InternalAffairA IaffairA;
        InternalAffairB IaffairB;
        InternalAffairC IaffairC;
        MilitaryAffairA MaffairA;
        MilitaryAffairB MaffairB;
        MilitaryAffairC MaffairC;

        public FacadeManagers()
        {
            IaffairA = new InternalAffairA();
            IaffairB = new InternalAffairB();
            IaffairC = new InternalAffairC();
            MaffairA = new MilitaryAffairA();
            MaffairB = new MilitaryAffairB();
            MaffairC = new MilitaryAffairC();
        }

        /// <summary>
        /// 可派魏延執行軍事
        /// </summary>
        public void MilitaryOperate()
        {
            MaffairA.InternalMethoad();
            MaffairB.InternalMethoad();
            MaffairC.InternalMethoad();
        }

        /// <summary>
        /// 可派蔣婉管理內政
        /// </summary>
        public void InternalAffairOperate()
        {
            IaffairA.InternalMethoad();
            IaffairB.InternalMethoad();
            IaffairC.InternalMethoad();
        }
    }

我們定義了FacadeManagers(政務官)的類別,由這個類別來認識所有子系統的類別和方法,並宣告了MilitaryOperate方法(代表魏延)負責軍事子系統的相關功能執行;再宣告InternalAffairOperate(代表蔣婉)負責內政子系統的相關功能執行。

 class Program
    {
        static void Main(string[] args)
        {
            var Managers = new FacadeManagers();
            Managers.InternalAffairOperate();
            Managers.MilitaryOperate();

            Console.Read();
        }
    }

接下來我們再來看Main(諸葛亮),我們可以發現Main只需要認識FacadeManager(政務官)就可以了,所以如果子系統有異動,只有FacadeManager需要配合修正,而完全不需要更動到主程式的類別。 

大家看到這,應該可以知道外觀模式的威力了,但可能還會有一點疑問,比如說何時可以使用這個設計模式呢?最常見的就是當子系統因不斷的重構或者新增功能,可能會產生很多方法,以本篇的例子,軍事的子系統除了"運糧補給"和"訓練士兵"跟"調兵遣將"外,之後會不會又有"招募士兵","拜訪武將","防守城池"等等方法出現,如果能定義一個Facade類別,將可以大大減少隅和,並更方便地使用;另外一個時機點便是新系統要跟舊系統互動,但你可能1.不想花很多時間更改舊系統,也有可能2.不想讓新系統依賴舊系統那堆噁心的架構或者方法的實作。PS.如果你能忍受第二點,我的建議是也不需要寫新系統了。在這個時機點,Facade就會非常好用,把那些噁心的依賴封裝在Facade而讓新系統可以透過Facade簡潔的介面來操作,可以大大提升新系統未來的可維護性,而不會被舊系統牽著鼻子走。

好了,設計模式說完了,那大家覺得諸葛亮會接受這個建議嗎?我覺得還是不會,因為諸葛亮表示:唯恐他人不似我盡心阿。其實,好的團隊應該先從傳球做起,但諸葛亮並不是為了自己的利益才這樣做,而是有更崇高的理想,因此這裡也就不忍苛責了。