話三國~外觀模式
五丈原諸葛禳星
諸葛亮六出祁山北伐曹魏,但始終無法解決糧草問題而無功而返。為了避免夜長夢多,諸葛亮必須迫使魏軍決一死戰,但司馬懿老奸巨猾,也深知用兵之道,知道時間站在自己的這一邊,因此始終堅守不出。諸葛亮心生一計,便派遣使者以送禮為名,贈送司馬懿巾幗服裝,嘲諷其不敢出戰,乃一婦人也!豈知司馬懿不但不動怒,還當眾穿起,並問使者這樣穿好不好看?司馬懿的部將皆目露殺氣,看主帥受辱,隨時準備拔劍斬殺來使,唯獨司馬懿非常淡定並閒聊諸葛亮近況,使者據實以告,司馬懿笑道:食少事煩,豈能長久。蜀使回歸後,將情形告知諸葛亮,諸葛亮歎曰:彼深知我心也。
蜀漢主簿楊儀諫曰:我見丞相常常親自處理很多瑣事,我認為這非常不好!上下不可相侵,你身為丞相,只需交辦屬下做事即可,若凡事親力親為,最後將搞死自己而一事無成。孔明泣曰:我並不是不知道這個道理,但受到先帝托孤,我怕其他人不像我如此盡心盡力!眾將聽了皆暗自垂淚,按下不表。
諸葛亮一直是我非常喜歡的歷史人物之一,每次看到這段歷史故事,我都會認為劉備有部下如此,真的是死而無憾。但反過來講,諸葛亮犯了什麼錯?以設計原則來看,我認為諸葛亮凡事親力親為,因此認識太多實作類別,相依性太重,所以一旦諸葛亮這個類別掛了,蜀漢這個系統就瀕臨崩壞的危險。
這樣說可能大家還有點模糊,我們用案例來看就會比較容易了解。首先,我們先定義兩個子系統,分別代表諸葛亮目前需要執行的內政工作和軍事工作,內政包含了徵收賦稅以及土地開墾和防災演練共三個類別;軍事包含了糧草補給以及訓練士兵和調兵遣將也是三個類別,而為了不複雜化,類別的方法我都定義的很簡單,大家可以直接往下看。
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簡潔的介面來操作,可以大大提升新系統未來的可維護性,而不會被舊系統牽著鼻子走。
好了,設計模式說完了,那大家覺得諸葛亮會接受這個建議嗎?我覺得還是不會,因為諸葛亮表示:唯恐他人不似我盡心阿。其實,好的團隊應該先從傳球做起,但諸葛亮並不是為了自己的利益才這樣做,而是有更崇高的理想,因此這裡也就不忍苛責了。