話三國~樣板方法模式
諸葛亮智算華容
話說,周瑜赤壁一把大火,燒掉曹操統一天下的夢想之後,諸葛亮也未閒著,正在積極的準備埋伏,要在陸戰徹底殲滅曹操.畫面轉到劉備軍營帳,孔明與劉備等坐定,便對趙雲說:子龍,你帶三千軍馬,在烏林小路埋伏,只可追擊,不可追死,避免敵軍拼死一戰,減少損傷.趙雲領命便退下了;孔明接著傳喚張飛:翼德可領三千兵在葫蘆口埋伏,曹操到的時候已是傍晚、必會埋鍋造飯,一看火起,便可殺出,就算不能抓到曹操,功勞也不小了.張飛聽完大喜,便下去準備;最後孔明又分別吩咐糜芳,劉封交代完任務,便準備散會了.這時候關羽終於按耐不住,上前說道:關某陪大哥征戰多年,未曾落後,今日如此大戰,豈可無我!孔明聽罷曰:我有一個重大任務,想要交給將軍,豈耐因將軍曾受曹操大恩,故此猶豫不決.關羽怒道,吾斬顏良、誅文醜,已報此恩,今日相見,公是公,私是私,豈可混淆!孔明道:口說無憑.關羽答:願立軍令狀!孔明大喜,便道:曹操今夜會經華容道,將軍可埋伏於該地,曹操到時人困馬乏,將軍坐下赤兔馬,曹操必定無法逃脫,就看今夜關將軍立此大功.關羽領命而去.
以上故事,我們先來看基本版的程式應該如何達成,首先趙雲得到命令,他必須要先做埋伏準備動作,1.先集合軍隊,2.準備武器糧草,3.全軍出發埋伏,最後等曹操到來才執行孔明的攻擊指令;接下來,張飛得到命令,也必須做埋伏準備動作1.先集合軍隊,2.準備武器糧草,3.全軍出發埋伏,最後等曹操到來才執行孔明的攻擊指令;接下來糜芳,劉封,關羽依此類推,就不贅述.因此程式碼大概長得像以下的樣子.
/// <summary>
/// 趙雲
/// </summary>
class Commander1
{
private bool armed = false;
public void Convene()
{
Console.WriteLine("部隊聽令 全軍集合");
}
public void Prepare()
{
armed = true;
Console.WriteLine("準備武器、糧草");
}
public void Depart()
{
Console.WriteLine("全軍出發");
}
public void Operate(string command)
{
if (armed)
{
Console.WriteLine("趙雲揮舞梨花槍:" + command);
}
else
{
Console.WriteLine("趙雲糧草不足 軍隊潰敗");
}
}
}
首先,先來看Commander1(趙雲)的程式碼,它具備了Convene(集合),Prepare(準備糧草),Depart(出發),Operate(執行孔明指令)的方法.
/// <summary>
/// 張飛
/// </summary>
class Commander2
{
private bool armed = false;
public void Convene()
{
Console.WriteLine("部隊聽令 全軍集合");
}
public void Prepare()
{
armed = true;
Console.WriteLine("準備武器、糧草");
}
public void Depart()
{
Console.WriteLine("全軍出發");
}
public void Operate(string command)
{
if(armed)
{
Console.WriteLine("張飛擺動丈八蛇矛:" + command);
}
else
{
Console.WriteLine("張飛糧草不足 軍隊潰敗");
}
}
}
接下來看Commander2(張飛)的程式碼,也是具備了Convene(集合),Prepare(準備糧草),Depart(出發),Operate(執行孔明指令)的方法.
/// <summary>
/// 關羽
/// </summary>
class Commander3
{
private bool armed = false;
public void Convene()
{
Console.WriteLine("部隊聽令 全軍集合");
}
public void Prepare()
{
armed = true;
Console.WriteLine("準備武器、糧草");
}
public void Depart()
{
Console.WriteLine("全軍出發");
}
public void Operate(string command)
{
if (armed)
{
Console.WriteLine("關羽舞動青龍偃月刀:" + command);
}
else
{
Console.WriteLine("關羽糧草不足 軍隊潰敗");
}
}
}
最後看Commander3(關羽)的程式碼,會發現也是具備了Convene(集合),Prepare(準備糧草),Depart(出發),Operate(執行孔明指令)的方法.看到這裡應該有發現,除了Operate(執行孔明指令)的方法之外,剩下的三個方法實作都是一模一樣的.
class Program
{
static void Main(string[] args)
{
var 趙雲 =new Commander1();
趙雲.Convene();
趙雲.Prepare();
趙雲.Depart();
趙雲.Operate("突擊曹操,只擊不追");
Console.WriteLine();
var 張飛 = new Commander2();
張飛.Convene();
張飛.Depart();
張飛.Operate("突擊曹操,只擊不追");
Console.WriteLine();
var 關羽 = new Commander3();
關羽.Convene();
關羽.Prepare();
關羽.Depart();
關羽.Operate("攔截曹操,務必生擒或當場擊殺");
Console.Read();
}
}
接下來看看真正執行的狀況,Commander1(趙雲)和Commander3(關羽)要埋伏之前執行了Convene(集合),Prepare(準備糧草),Depart(出發),之後才執行Operate(執行孔明指令)的方法;但Commander2(張飛)天生是個粗線條,他只有執行了Convene(集合),Depart(出發),忘記執行Prepare(準備糧草),就馬上執行Operate(執行孔明指令)的方法.
程式碼輸出:
部隊聽令 全軍集合
準備武器糧草
全軍出發
趙雲揮舞梨花槍:突擊曹操,只擊不追
部隊聽令 全軍集合
全軍出發
張飛糧草不足,全軍潰敗
部隊聽令 全軍集合
準備武器糧草
全軍出發
關羽舞動青龍偃月刀:攔截曹操,務必生擒或當場擊殺
這樣的輸出結果,很明顯不是我們預期的,因為張飛居然潰敗了.根據程式設計原則DRP,重複的程式碼是令人反感的;而且在實際使用該類別的時候,甚至有可能忘了執行某個方法,或者呼叫順序錯誤,而造成輸出結果有問題.這些都是我們事先就可以避免的事情.
由上面的程式實作方式可以看到,Convene(集合),Prepare(準備糧草),Depart(出發)三個方法功能都是一樣的,關羽張飛和趙雲的差別只有在Operate(執行孔明指令)的方法,而在設計模式中就有一種模式是透過將重複的程式碼上移到父類別,而子類別就能去除這些程式碼而專注於自己類別差異的部分.再深入一點來說,這些重複的程式碼就是"不會改變的行為",以本故事的埋伏工作來說,Convene(集合),Prepare(準備糧草),Depart(出發)三個方法是不會改變的,不管是哪位將軍來執行,都必須做這些事情,而且這三個方法是有順序性的,總不能先Depart(出發)才Prepare(準備糧草)吧?俗話說:三軍未動,糧草先行就是這個道理;而真正在作戰的行為則是可變的,比如說,趙雲使用梨花槍,關羽用青龍偃月刀,張飛使用丈八蛇矛等等.而當一個類別中,同時具備了可變的行為和不變的行為,只要把不變的行為抽離至父類別,便可讓子類別擺脫這些重複程式碼的糾纏.以下要介紹的便是樣板方法模式
定義
樣板方法模式=>定義演算法結構,將實作延遲到子類進行.使子類可以不改變演算法結構,透過實作特定步驟達到目的。
/// <summary>
/// 部隊指揮官
/// </summary>
abstract class ICommander
{
protected string _command = "";
public ICommander(string command) {
_command = command;
}
public void ExcuteCommand()
{
Convene();
Prepare();
Depart();
Operate(_command);
}
private void Convene()
{
Console.WriteLine("部隊聽令 全軍集合");
}
private void Prepare()
{
Console.WriteLine("準備武器、糧草");
}
private void Depart()
{
Console.WriteLine("全軍出發 前往指定地點");
}
protected abstract void Operate(string command);
}
要達成樣板方法模式,首先須定義一個ICommander(部隊指揮官)的父類別,將原本各將軍不變的方法,分別是Convene(集合),Prepare(準備糧草),Depart(出發)方法集合到這個類別來,並明確實作;除此之外,還需要將可變的行為也定義出來,本例便是Operate(執行孔明指令)的方法,並將它抽象化,因為這個方法,會被所有子類別override,所以不需要實做;最後,定義一個樣板方法,這個樣板方法會照定義的順序,依序來呼叫指定的方法,也就是樣板方法模式的核心,定義一個演算法的結構,透過定義抽象的方法來強制繼承的子類別實作,以達到擴充的目的.
/// <summary>
/// 趙雲
/// </summary>
class CommanderA : ICommander
{
public CommanderA(string command) : base(command)
{ }
protected override void Operate(string command)
{
Console.WriteLine("趙雲揮舞梨花槍" + command);
}
}
/// <summary>
/// 張飛
/// </summary>
class CommanderB : ICommander
{
public CommanderB(string command) : base(command)
{ }
protected override void Operate(string command)
{
Console.WriteLine("張飛擺動丈八蛇矛:" + command);
}
}
/// <summary>
/// 關羽
/// </summary>
class CommanderC : ICommander
{
public CommanderC(string command) : base(command)
{ }
protected override void Operate(string command)
{
Console.WriteLine("關羽舞動青龍偃月刀:" + command);
}
}
接下來各將軍的類別只需要繼承ICommander,並且override Operate(執行孔明指令)的方法,其餘的功能,便可藉由繼承獲得,程式碼相對的變了簡潔許多.
class Program
{
static void Main(string[] args)
{
var 趙雲 =new CommanderA("突擊曹操,只擊不追");
var 張飛 = new CommanderB("突擊曹操,只擊不追");
var 關羽 = new CommanderC("攔截曹操,務必生擒或當場擊殺");
趙雲.ExcuteCommand();
Console.WriteLine();
張飛.ExcuteCommand();
Console.WriteLine();
關羽.ExcuteCommand();
Console.Read();
}
}
最後,再來看主程式碼,透過樣板方法模式,演算法的順序不但被固定下來,而且也不怕漏掉哪個必要的程序,而造成輸出結果有問題,同時整個程式碼也簡潔了不少.
程式碼輸出:
部隊聽令 全軍集合
準備武器糧草
全軍出發
趙雲揮舞梨花槍:突擊曹操,只擊不追
部隊聽令 全軍集合
準備武器糧草
全軍出發
張飛擺動丈八蛇矛:突擊曹操,只擊不追
部隊聽令 全軍集合
準備武器糧草
全軍出發
關羽舞動青龍偃月刀:攔截曹操,務必生擒或當場擊殺
以上便是樣板方法模式的心得分享...