話三國~原型模式
發矯詔諸鎮應曹公
曹操自從刺殺董卓失敗之後,逃出洛陽,為了天下蒼生,便下定決心要假借天子詔的名義廣發檄文爭討董卓.曹操清點了一下目前有勢力可以加入討董行列的諸侯,如下列名單所示:第一鎮,後將軍南陽太守袁術。第二鎮,冀州刺史韓馥。第三鎮,豫州刺史孔鈾。第四鎮,兗州刺史劉岱。第五鎮,河內郡太 守王匡。第六鎮,陳留太守張邈。第七鎮,東郡太守喬瑁。第八鎮,山陽太守袁遺 。第九鎮,濟北相鮑信。第十鎮,北海太守孔融。第十一鎮,廣陵太守張超。第十二鎮,徐州刺史陶謙。第十三鎮,西涼太守馬騰。第十四鎮,北平太守公孫瓚。第 十五鎮,上黨太守張楊。第十六鎮,烏程侯長沙太守孫堅。第十七鎮,祁鄉侯渤海太守袁紹加上自己共十八鎮諸侯.
檄文的格式如下
袁紹聽令(Owner)
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 (Context)
漢獻帝印 欽此 (Footer)
以上故事,就直接用最簡單的方式來寫看看吧.
/// <summary>
/// 征討檄文
/// </summary>
class ConveneDocument
{
private string _owner;//發送對象
private string _context;//內文
private string _footer;//註腳
public ConveneDocument(string context, string footer)
{
_context = context;
_footer = footer;
}
public void SetOwner(string name)
{
_owner = name + "聽令!! ";
StringBuilder command = new StringBuilder();
command.Append(_owner);
command.Append(_context);
command.Append(_footer);
Console.WriteLine(command.ToString());
}
}
首先,定義ConveneDocument(檄文)的類別,在建構子裡接收兩個參數,來設定Context和Footer;並且提供一個SetOwner方法來指定並發送給特定對象.
static void Main(string[] args)
{
var 檄文1 = new ConveneDocument("奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 ", "漢獻帝印 欽此");
檄文1.SetOwner("孫堅");
var 檄文2 = new ConveneDocument("奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 ", "漢獻帝印 欽此");
檄文2.SetOwner("袁術");
var 檄文3 = new ConveneDocument("奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 ", "漢獻帝印 欽此");
檄文3.SetOwner("公孫讚");
var 檄文4 = new ConveneDocument("奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 ", "漢獻帝印 欽此");
檄文4.SetOwner("袁紹");
Console.Read();
}
接著來寫主程式,曹操在這裡只發了4封檄文,就覺得有點寫不下去了,因為密密麻麻的重複程式碼,等到把18路諸侯的檄文都寫齊,恐怕已經是一個災難.如果檄文的內容有要做修正,那一改就是18個地方要改,這實在是一件非常蠢的事.此時,曹操心想,假如能把檄文內容直接複製一份,然後把發送對象改掉就好,那一切將會變得容易許多.而在設計模式裡,原型模式就是在專處理這種情境.
定義
原型模式=>透過拷貝的方式來建立新物件,而不需要再對新物件基於原型基礎拷貝來的屬性重新做設定。
/// <summary>
/// 征討檄文
/// </summary>
class ConveneDocument : ICloneable//要實作ICloneable介面
{
private string _context;
private string _footer;
private string _owner;
public ConveneDocument(string context, string footer)
{
_context = context;
_footer = footer;
}
//ICloneable介面定義的方法
public object Clone()
{
return this.MemberwiseClone();
}
public void SetOwner(string name)
{
_owner = name + "聽令!! ";
StringBuilder command = new StringBuilder();
command.Append(_owner);
command.Append(_context);
command.Append(_footer);
Console.WriteLine(command.ToString());
}
}
實作的方式非常簡單,只需要將原本的類別標記成有實作ICloneable介面,接著實作Clone方法,而實作的方式直接呼叫MemberwiseClone()方法即可.
class Program
{
static void Main(string[] args)
{
var 檄文1 = new ConveneDocument("奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 ", "漢獻帝印 欽此");
檄文1.SetOwner("孫堅");
var 檄文2 = 檄文1.Clone() as ConveneDocument;
檄文2.SetOwner("袁術");
var 檄文3 = 檄文1.Clone() as ConveneDocument;
檄文3.SetOwner("公孫讚");
var 檄文4 = 檄文1.Clone() as ConveneDocument;
檄文4.SetOwner("袁紹");
Console.Read();
}
}
主程式透過呼叫Clone的方法,便可拷貝Context和Footer而不需要再重新做設定,程式碼簡潔了不少,並且也達到了預期的功能.附帶一提,原型模式還有一個很適用的情境,便是假如某物件的建構需要很長的時間,透過簡單的Clone方式可以迅速地建立新物件,並且保有基於原型模式的所有設定.
就當一切運作的都非常順利的時候,此時出現了一個新需求,除了發送檄文告知對方前來之外,曹操希望能夠同時告知對方需要帶領多少軍力和將軍前來.
/// <summary>
/// 軍隊數量
/// </summary>
class ArmyAmount
{
public int soilder = 0;//兵力數量
public int general = 0;//將軍數量
public ArmyAmount(int soilder,int general)
{
this.soilder = soilder;
this.general = general;
}
}
為了達到此一需求,我們宣告了一個ArmyAmount類別,來存放需要率領多少將軍和士兵的資訊.
/// <summary>
/// 征討檄文
/// </summary>
class ConveneDocument : ICloneable
{
private string _context;
private string _footer;
private string _owner;
public ArmyAmount army=new ArmyAmount(0,0);
public ConveneDocument(string context, string footer)
{
_context = context;
_footer = footer;
}
public object Clone()
{
var result = this.MemberwiseClone() as ConveneDocument;
return result;
}
//新增設定軍隊規模的方法
public void SetArmy(int general,int soilder)
{
army.general = general ;
army.soilder = soilder;
}
public void SetOwner(string name) {
_owner = name + "聽令!! ";
Console.WriteLine(_owner);
Console.WriteLine(_context);
//新增軍隊規模的輸出
Console.WriteLine("令率戰將" + army.general + " 大軍" + army.soilder);
Console.WriteLine(_footer);
Console.WriteLine();
}
}
並且在ConveneDocument類別新增SetArmy的方法用來設定ArmyAmount(軍隊數量),並且在SetOwner的地方額外輸出共率領多少軍隊的字樣.
class Program
{
static void Main(string[] args)
{
var 檄文1 = new ConveneDocument("奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓 ", "漢獻帝印 欽此");
檄文1.SetArmy(10,30000);
檄文1.SetOwner("孫堅");
Console.WriteLine();
var 檄文2 = 檄文1.Clone() as ConveneDocument;
檄文2.SetArmy(5, 20000);
檄文2.SetOwner("袁術");
Console.WriteLine();
var 檄文3 = 檄文1.Clone() as ConveneDocument;
檄文3.SetArmy(2, 10000);
檄文3.SetOwner("公孫讚");
Console.WriteLine();
var 檄文4 = 檄文1.Clone() as ConveneDocument;
檄文4.SetArmy(20, 60000);
檄文4.SetOwner("袁紹");
Console.WriteLine();
Console.Read();
}
}
主程式同樣透過呼叫Clone的方法,拷貝Context和Footer而不需要再重新做設定,之後呼叫SetArmy(設定軍隊規模)和SetOwner(發送檄文給指定對象).我們來看看輸出.
程式碼輸出:
孫堅聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將20 大軍60000
漢獻帝印 欽此
袁術聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將20 大軍60000
漢獻帝印 欽此
公孫瓚聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將20 大軍60000
漢獻帝印 欽此
袁紹聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將20 大軍60000
漢獻帝印 欽此
所有諸侯都叫苦聊天,因18鎮諸侯,屬袁紹勢力最大,也是唯一有能力動員大軍60000,戰將20人的諸侯,可問題是曹操在設計檄文的時候,本來就有根據各諸侯可動員的兵力來做檄文的撰寫,怎會到後來全部檄文的軍隊規模都變成袁紹的版本呢?其實,這問題並不難理解,就是複製value和複製reference的問題.如果是使用MemberwiseClone,針對物件只會拷貝reference,也就造成其實所有諸侯的army屬性都是指向同一個ArmyAmount的物件,而好死不死,袁紹又是最後一個做army設定的,導致所有諸侯的軍隊規模版本都變成袁紹的版本.那要如何實作出連物件的拷貝都是拷貝成新的物件實體呢?接著就來看看下面的程式碼.
/// <summary>
/// 軍隊數量
/// </summary>
class ArmyAmount:ICloneable//也要宣告實作ICloneable介面
{
public int general = 0;
public int soilder = 0;
public ArmyAmount(int general,int soilder)
{
this.general = general;
this.soilder = soilder;
}
//實作方式跟前面一樣
public object Clone()
{
return this.MemberwiseClone();
}
}
針對ArmyAmount(軍隊數量)的物件必須也要實作ICloneable介面,並且透過呼叫MemberwiseClone來實作Clone方法.
/// <summary>
/// 征討檄文
/// </summary>
class ConveneDocument : ICloneable
{
private string _context;
private string _footer;
private string _owner;
public ArmyAmount army=new ArmyAmount(0,0);
public ConveneDocument(string context, string footer)
{
_context = context;
_footer = footer;
}
public object Clone()
{
var result = this.MemberwiseClone() as ConveneDocument;
//army的拷貝也是採用拷貝新的實體而非只有參考
result.army = result.army.Clone() as ArmyAmount;
return result;
}
public void SetArmy(int general,int soilder)
{
army.general = general ;
army.soilder = soilder;
}
public void SetOwner(string name) {
_owner = name + "聽令!! ";
Console.WriteLine(_owner);
Console.WriteLine(_context);
//新增軍隊規模的輸出
Console.WriteLine("令率戰將" + army.general + " 大軍" + army.soilder);
Console.WriteLine(_footer);
Console.WriteLine();
}
}
原本的檄文類別只需要做小修改,Clone的方法額外新增針對Army屬性是呼叫ArmyAmount類別所提供的Clone方法,如此便能達成不是拷貝物件參考,而是完完全全拷貝出一個新實體的需求.接著便可以直接來看程式碼的輸出.
程式碼輸出:
孫堅聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將10 大軍30000
漢獻帝印 欽此
袁術聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將5 大軍20000
漢獻帝印 欽此
公孫瓚聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將2 大軍10000
漢獻帝印 欽此
袁紹聽令
奉天承運,皇帝詔曰:令爾等即刻出兵征伐董卓
令率戰將20 大軍60000
漢獻帝印 欽此
完成,曹操看到檄文的內容非常滿意,而各諸侯也真的如期前來赴約,看來剿滅董卓的日子指日可待了,以上便是原型模式的簡單分享....