話三國~裝飾者模式
發矯詔諸鎮應曹公
話說,曹操刺殺董卓失敗,落跑時被守將陳宮捉住。操曉以大義,董卓人神共憤,陳宮被其志氣感動,棄官追隨,但在路途上,曹操因誤會好友呂伯奢的家丁想要謀害於他,全數殺之後才驚覺,此乃誤會一場,只好與陳宮再次出逃,不料竟在路上遇到呂伯奢,呂伯奢道:家裡已殺豬宰羊要款待,怎那麼急著走呢。操因自知理虧,隨意敷衍一番,突然手起刀落將呂伯奢斬於馬下。陳宮大驚怒道:剛才誤殺已屬不對,如今為何又下此毒手?操回道:呂伯奢一旦回去發現族人被殺之殆盡,定會懷恨在心,於你我不利。寧可我負天下人,勿叫天下人負我!宮不語。因日已晚,兩人便到一客棧投宿,按下不表。
夜裡。陳宮暗想,若讓此人成霸者之業,豈非與董卓無異!吾不願跟隨。思考完後,便決定離開曹操,臨行之時欲下手殺曹操,忽轉念:我為國家跟他到此,殺之也屬不義。不若 棄而他往。插劍上馬,不等天明,自投東郡去了。第二天曹操醒來,找不到陳宮,心想:真腐儒也,成大事者不拘小節,現在世道還講禮義廉恥,隔天怎死的都不知道!曹操不以為意便單身前往老家陳留 ,見著父親,便對父親說自己的狀況,並告知希望能變賣家產,號召天下英雄討賊。父言:以天下為重,便同意曹操的請求。操大喜;於是便以曹操軍的名義招集義兵,豎起招兵白旗一面,上面寫著「忠義」二字。才短短幾天。有沛國譙人夏侯惇 ,字元讓,乃夏侯嬰之後;自小習槍棒;年十四從師學武,有人辱罵其師,惇殺之後逃跑;聞知曹操起兵,立刻召集了三千鐵騎來投靠。接著,曹氏兄 弟曹仁弓馬熟嫻,武藝精通也引軍來投。除此之外,謀士荀彧也來投靠,此人有經天緯地之才,將為曹操的基業打下良好的基礎。最後,各地的富商也紛紛響應,大筆經費解決了曹操軍的補給問題。曹操大喜,知董卓暴政必亡!時代造英雄,這將是自己很好的一次崛起機會。
曹操憑藉著驚人的毅力 ,竟然真的扛起來一面王旗, 號招天下英雄加入了他的陣營。那曹操到底是怎麼做到的呢?我們就以設計模式的角度分析看看吧。首先,我們先假設有一個叫做曹操軍的類別代表著曹操,曹操軍需要猛將,需要謀士,當然也需要錢,那代表要建立曹操軍,需要加入猛將、謀士、和金錢。那這樣需要使用甚麼模式呢?一定有人會看到建造兩個字,便覺得需要使用建造者模式。但我這裡要特別說明一下,建造者模式在於有著穩定的建造流程,曹操在豎起王旗的時候,他不可能知道誰會來投靠他,也不應該知道!因為,這樣一來就會被限制住了,天下英雄何其多也。所以這裡講到關鍵字"不穩定",而有一種模式叫做"裝飾者"便是專門在處理這種不穩定的為原本類別添加新功能的解決方案,我們可以想像一下曹操軍原本的功能可能只有"曹操刺殺董卓"的方法,但這樣的功能是無法擊敗董卓的,歷史也證明曹操失敗了,所以我們需要再額外替曹操軍擴充新的功能,比如,率兵打仗阿,運籌帷幄阿,收集軍費阿等等的。我們這就來看看裝飾者模式的定義。
定義
裝飾者模式:動態的替一個類別添加新的功能,就增加功能來說,裝飾者模式將比增加子類別更具彈性
/// <summary>
/// 軍隊
/// </summary>
interface IArmy
{
//召集
void ConveneOperation();
}
那我們就來直接看看程式範例吧,首先,我們先宣告IArmy介面,我們就當作是軍隊的介面,IArmy定義了一個ConveneOperation(召集天下英雄的方法),IArmy可以當作是三國時代所有諸侯軍隊的抽象概念。相信每個諸侯的心願都是能夠召集天下英雄來投靠,像劉備手下就有關羽、張飛;董卓手下有呂布等等。
/// <summary>
/// 曹操軍
/// </summary>
class Army : IArmy
{
public void ConveneOperation()
{
Console.WriteLine("曹操軍號召!!!");
}
}
接下來便要定義故事中的主角曹操軍,就簡單定義成Army,實做了IArmy。當然如果要宣告其他軍團也是同樣的方式,比如說要宣告董卓軍,只要實做了IArmy,便可完成董卓軍的類別宣告。
/// <summary>
/// 將軍的抽象 可以是任何一位三國名將
/// </summary>
abstract class AGeneral : IArmy
{
protected IArmy _general;
public void setGeneral (IArmy general)
{
_general = general;
}
public virtual void ConveneOperation();
}
接著,我們要定義的是三國的英雄豪傑的抽象類別AGeneral,他們都可以動態的幫IArmy(軍隊)添加額外的功能,首先,AGeneral也必須實作IArmy的介面,但並不直接實作原本IArmy的ConveneOperation方法,而將具體的實作交給子類別擴充。除此之外,還定義一個方法SetGeneral,接受任何實作IArmy的類別,為的是要裝飾原本的類別。
/// <summary>
/// 曹仁
/// </summary>
class GeneralA:AGeneral
{
public override void ConveneOperation()
{
base.ConveneOperation();
Console.WriteLine("曹仁:吾有萬夫不當之勇 願成為主公堅實的後盾");
}
}
/// <summary>
/// 荀彧
/// </summary>
class GeneralB :AGeneral
{
public override void ConveneOperation()
{
base.ConveneOperation();
Console.WriteLine("荀彧:吾有經天緯地之才 願為主公出謀獻策 ");
}
}
/// <summary>
/// 夏侯惇
/// </summary>
class GeneralC:AGeneral
{
public override void ConveneOperation()
{
base.ConveneOperation();
Console.WriteLine("夏侯惇:吾有精銳鐵騎3000 願為主公衝鋒陷陣");
}
}
/// <summary>
/// 富商
/// </summary>
class GeneralD :AGeneral
{
public override void ConveneOperation()
{
base.ConveneOperation();
Console.WriteLine("富商:我有萬貫家財 願為主公募集經費");
}
}
接著,就是宣告所有可以動態裝飾軍隊的英雄豪傑,只需額外override繼承而來的抽象ConveneOperation方法,實作自己類別特有的邏輯,之後便可動態裝飾到所有實做了IArmy類別上。
class Program
{
static void Main(string[] args)
{
IArmy 曹操軍 = new Army();
var 曹仁 = new GeneralA();
var 荀彧 = new GeneralB();
var 夏侯惇 = new GeneralC();
var 富商 = new GeneralD();
曹仁.setGeneral(曹操軍);
荀彧.setGeneral(曹仁);
夏侯惇.setGeneral(荀彧);
富商.setGeneral(夏侯惇);
富商.ConveneOperation();
Console.Read();
}
}
最後,我們來看怎麼達到的,首先,宣告一個Army的類別作為曹操軍,然後宣告四個實作AGenera的l英雄豪傑子類(曹仁GeneralA、荀彧GeneralB、夏侯惇GeneralC、富商GeneralD),接著透過SetGeneral方法便可以不斷動態擴充既有類別的功能。再由做後一個General類別執行ConveneOperation方法,很神奇的事情就會發生了,請直接看輸出。
最後輸出
曹操軍號召!!!
曹仁:吾有萬夫不當之勇 願成為主公堅實的後盾
荀彧:吾有經天緯地之才 願為主公出謀獻策
夏侯惇:吾有精銳鐵騎3000 願為主公衝鋒陷陣
富商:我有萬貫家財 願為主公募集經費
裝飾者模式充滿了彈性,從上方輸出可以發現,曹操軍的類別,被很快速的添加了四個功能,但本身曹操軍其實跟那四個將軍是不相關的。再舉一個例子,假如還有另外一個場景,今天曹操軍需要與董卓軍對壘,能上陣的我覺得只有曹仁和夏侯惇,你總不能派軍師去衝鋒陷陣,商人更別說了,你把他丟前線,一定一會就陣亡了,那錢還哪裡來?那這個需求該如何做?如果使用裝飾者模式,只需要將程式稍做修改便可滿足需求。
class Program
{
static void Main(string[] args)
{
IArmy 曹操軍 = new Army();
var 曹仁 = new GeneralA();
var 荀彧 = new GeneralB();
var 夏侯惇 = new GeneralC();
var 富商 = new GeneralD();
曹仁.setGeneral(曹操軍);
//荀彧.setGeneral(曹仁);
夏侯惇.setGeneral(曹仁);
//富商.setGeneral(夏侯惇);
夏侯惇.ConveneOperation();
Console.Read();
}
}
我們簡單註解掉荀彧和富商那兩行程式,並把夏後惇略過荀彧直接裝飾在曹仁身上,便大功告成了。直接來看輸出
最後輸出
曹操軍號召!!!
曹仁:吾有萬夫不當之勇 願成為主公堅實的後盾
夏侯惇:吾有精銳鐵騎3000 願為主公衝鋒陷陣
是不是非常方便呢?如果在實際應用上,裝飾者模式用在驗證相關的邏輯也是一個不錯的選擇,可以把驗證邏輯抽成一個又一個的類別,然後再根據你當下的情況,決定要動態添加那些驗證,便可以滿足需求。好了,今天的故事就到這邊囉,希望大家能充分了解裝飾者模式的好用之處,感謝大家,下回再見。