前面講了創建一個對象實例的方法單例模式Singleton Parttern, 創造多個產品的工廠模式(簡單工廠模式 Simple Factory Pattern, 工廠方法模式 FactoryMothed Parttern,抽象工廠模式 Abstract Factory Method),以及創建複雜 ...
前面講了創建一個對象實例的方法單例模式Singleton Parttern, 創造多個產品的工廠模式(簡單工廠模式 Simple Factory Pattern, 工廠方法模式 FactoryMothed Parttern,抽象工廠模式 Abstract Factory Method),以及創建複雜對象的建造者模式 Builder Pattern, 這几几乎包含了產品創建的各個方方面,但是還有一種,那就是有自我創建能力的模式,這種模式能夠創建出和自己相同或者相似的對象。生活中經常也會見到這方面的例子,比如蠕蟲病毒的自我複製,細胞分裂以及自我繁殖,游戲角色的自我複製和分身等。
在軟體開發中也會經常遇到這樣的問題,最近在做項目的時候就碰到了這麼一個需求,問卷調查試卷復用的問題。我們的系統用戶組成是這樣的,系統有一個超級管理員的角色,超級管理員可以乾任何事情,系統中還接入了N多公司,每個公司有公司的管理員,公司管理員可以乾超級管理員分配給該公司的相應許可權, 那麼這個問卷調查的需求是這樣的:
1. 超級管理員可以創建問卷調查試卷,但是這個問卷調查的試卷不能直接使用,只能供各公司的管理員作為模板樣例創建自己公司的問卷調查試卷。
2.各個公司的管理員可以創建自己公司的問卷調查試卷,僅供自己公司員工使用。
3.各個公司的管理員可以可以基於超級管理員創建的問卷調查試卷創建自己的模板,創建出來的問卷調查試卷僅供自己公司的員工使用。
4.公司管理員不能修改超級管理員創建的調查問卷試卷。
5. 超級管理員可以修改自己的問卷調查試卷, 並且不會影響各個公司之前根據問卷調查試卷創建出來的試卷。
需求用文字描述出來有點費勁,看下麵這張圖:
該怎麼來實現呢? 這就是本文要討論的主角原型模式Pototype Parttern ,也是最後一個創建型模式,原型模式就是為解決這類問題而生的:)。
一、原型模式的定義
原型模式(Prototype Pattern):使用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。原型模式是一種對象創建型模式
二、原型模式的結構
1、Prototype(抽象原型類):
它是聲明克隆方法的介面,是所有具體原型類的公共父類,可以是抽象類也可以是介面,甚至還可以是具體實現類。
2、ConcretePrototype(具體原型類):
它實現在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象。
3、Client(客戶類):
讓一個原型對象克隆自身從而創建一個新的對象,在客戶類中只需要直接實例化或通過工廠方法等方式創建一個原型對象,再通過調用該對象的克隆方法即可得到多個相同的對象。由於客戶類針對抽象原型類Prototype編程,因此用戶可以根據需要選擇具體原型類,系統具有較好的可擴展性,增加或更換具體原型類都很方便。
三、原型模式的經典實現
原型模式的核心是克隆方法的實現:
1、通用實現方法
通用的克隆實現方法是在具體原型類的克隆方法中實例化一個與自身類型相同的對象並將其返回,並將相關的參數傳入新創建的對象中,保證它們的成員屬性相同:
public abstract class Pototype { public string Some { get; set; } public abstract Pototype Clone(); } public class ConcretePototype : Pototype { public override Pototype Clone() { Pototype pototype = new ConcretePototype(); pototype.Some = this.Some; return pototype; } }
客戶端調用:
static void Main(string[] args) { Structure.Pototype pototype = new ConcretePototype(); pototype.Some = "pototype"; Structure.Pototype clone = pototype.Clone(); Console.WriteLine("I'm old "+pototype.Some); Console.WriteLine("I'm clone "+clone.Some); Console.ReadKey(); }
輸出結果:
2、C#實現
C# 中有一個拷貝的方法,在克隆中我們用MemberwiseClone它來克隆一個對象:
public class CsharpPototype : Pototype { public override Pototype Clone() { return this.MemberwiseClone() as Pototype; } }
客戶端調用:
static void Main(string[] args) { Structure.Pototype pototype = new CsharpPototype(); pototype.Some = "pototype"; Structure.Pototype clone = pototype.Clone(); Console.WriteLine("I'm old "+pototype.Some); Console.WriteLine("I'm clone "+clone.Some); Console.ReadKey(); }
客戶端輸出和上面一樣, 這種方式實現的是淺拷貝。
深拷貝和淺拷貝, 淺拷貝如果原型對象的成員是值類型那麼就將原型對象複製一份給克隆對象,如果是引用類型,只將原型對象的地址複製一份給克隆對象,這時克隆對象和原型對象在記憶體中指向同一個對象,修改其中的一個會影響另一個。深拷貝,則是將原型對象拷貝一份給克隆對象,克隆對象和原型對象在記憶體中有獨立的存儲空間,一個改動了不會影響另一個。
四、原型模式實例
現在我們弄明白了原型模式,現在就來實現開頭提出的問卷調查試卷創建的需求,因為試卷的內容比較複雜這裡我們是找出一些核心的能夠說明問題的模型來演示這個例子。我們先將試卷的內容假定為字元串類型。
這裡我們將超級管理員創建的試卷命名為SuperSurveyPaper。
這裡SuperSurveyPaper 充當抽象原型類,CompanyASurveyPaper 和CompanyBSurveyPaper 充當具體原型類。
1、淺拷貝實現:
public abstract class SuperSurveyPaper { public string Name { get; set; } public string Content { get; set; } public abstract SuperSurveyPaper Clone(); } public class CompanyASurveyPaper : SuperSurveyPaper { public override SuperSurveyPaper Clone() { return this.MemberwiseClone() as SuperSurveyPaper; } } public class CompanyBSurveyPaper : SuperSurveyPaper { public override SuperSurveyPaper Clone() { return this.MemberwiseClone() as SuperSurveyPaper; } }
客戶端調用代碼:
static void Main(string[] args) { PototypeInstance.SuperSurveyPaper pototype = new CompanyASurveyPaper(); pototype.Name = "SuperSurveyPaper ->Name"; pototype.Content = "SuperSurveyPaper -> Content"; PototypeInstance.SuperSurveyPaper clone = pototype.Clone(); Console.WriteLine("I'm old Name: " + pototype.Name); Console.WriteLine("I'm old Content: " + pototype.Content); Console.WriteLine("======================== ==========="); Console.WriteLine("I'm Clone Name: " + clone.Name); Console.WriteLine("I'm Clone Content: " + clone.Content); Console.ReadKey(); }
輸出結果:
這裡也可以加入配置通過反射來創建原型對象
在app.config 中加入配置:
<appSettings> <add key="Pototype" value="DesignPattern.Pototype.PototypeInstance.CompanyBSurveyPaper"/> </appSettings>
調用段代碼改成:
static void Main(string[] args) { PototypeInstance.SuperSurveyPaper pototype; var setting = ConfigurationSettings.AppSettings["Pototype"]; var obj = Type.GetType(setting); if (obj == null) return; pototype = Activator.CreateInstance(obj) as PototypeInstance.SuperSurveyPaper; if (pototype == null) return; pototype.Name = "SuperSurveyPaper ->Name"; pototype.Content = "SuperSurveyPaper -> Content"; PototypeInstance.SuperSurveyPaper clone = pototype.Clone(); Console.WriteLine("I'm old Name: " + pototype.Name); Console.WriteLine("I'm old Content: " + pototype.Content); Console.WriteLine("======================== ==========="); Console.WriteLine("I'm Clone Name: " + clone.Name); Console.WriteLine("I'm Clone Content: " + clone.Content); Console.ReadKey(); }
輸出結果和上面一樣
2、深拷貝
如果現在的試卷內容不是一個簡單的字元串了而是一個對象:
[Serializable] public class SurveyPaperModel { public string FirstName { get; set; } public string LastName { get; set; } } [Serializable] public abstract class SuperSurveyPaper { public string Name { get; set; } public SurveyPaperModel Content { get; set; } public abstract SuperSurveyPaper Clone(); } [Serializable] public class CompanyASurveyPaper : SuperSurveyPaper { public override SuperSurveyPaper Clone() { MemoryStream memoryStream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memoryStream, this); memoryStream.Position = 0; return formatter.Deserialize(memoryStream) as SuperSurveyPaper; } } [Serializable] public class CompanyBSurveyPaper : SuperSurveyPaper { public override SuperSurveyPaper Clone() { MemoryStream memoryStream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memoryStream, this); memoryStream.Position = 0; return formatter.Deserialize(memoryStream) as SuperSurveyPaper; } }
客戶端調用:
static void Main(string[] args) { PototypeInstance.SuperSurveyPaper pototype; var setting = ConfigurationSettings.AppSettings["Pototype"]; var obj = Type.GetType(setting); if (obj == null) return; pototype = Activator.CreateInstance(obj) as PototypeInstance.SuperSurveyPaper; if (pototype == null) return; pototype.Name = "SuperSurveyPaper ->Name"; pototype.Content = new SurveyPaperModel { FirstName = "Design", LastName = "Pattern" }; PototypeInstance.SuperSurveyPaper clone = pototype.Clone(); Console.WriteLine("I'm old Name: " + pototype.Name); Console.WriteLine("I'm old Content: " + pototype.Content.FirstName); Console.WriteLine("I'm old Content: " + pototype.Content.LastName); Console.WriteLine("======================== ==========="); Console.WriteLine("I'm Clone Name: " + clone.Name); Console.WriteLine("I'm Clone Content: " + clone.Content.FirstName); Console.WriteLine("I'm Clone Content: " + clone.Content.LastName); Console.WriteLine("pototype==clone:" + clone.Equals(pototype)); Console.ReadKey(); }
輸出結果:
五、原型模式的優點
- 當創建新的對象實例較為複雜時,使用原型模式可以簡化對象的創建過程,通過複製一個已有實例可以提高新實例的創建效率。
- 擴展性較好,由於在原型模式中提供了抽象原型類,在客戶端可以針對抽象原型類進行編程,而將具體原型類寫在配置文件中,增加或減少產品類對原有系統都沒有任何影響。
- 原型模式提供了簡化的創建結構,工廠方法模式常常需要有一個與產品類等級結構相同的工廠等級結構,而原型模式就不需要這樣,原型模式中產品的複製是通過封裝在原型類中的克隆方法實現的,無須專門的工廠類來創建產品。
- 可以使用深克隆的方式保存對象的狀態,使用原型模式將對象複製一份並將其狀態保存起來,以便在需要的時候使用(如恢復到某一歷史狀態),可輔助實現撤銷操作。
六、原型模式的缺點
- 需要為每一個類配備一個克隆方法,而且該克隆方法位於一個類的內部,當對已有的類進行改造時,需要修改源代碼,違背了“開閉原則”。
- 在實現深克隆時需要編寫較為複雜的代碼,而且當對象之間存在多重的嵌套引用時,為了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來可能會比較麻煩。
七、原型模式的使用場景
- 創建新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網路資源),新的對象可以通過原型模式對已有對象進行複製來獲得,如果是相似對象,則可以對其成員變數稍作修改。
- 如果系統要保存對象的狀態,而對象的狀態變化很小,或者對象本身占用記憶體較少時,可以使用原型模式配合備忘錄模式來實現。
- 需要避免使用分層次的工廠類來創建分層次的對象,並且類的實例對象只有一個或很少的幾個組合狀態,通過複製原型對象得到新實例可能比使用構造函數創建一個新實例更加方便
八、擴展-原型管理器
原型管理器(Prototype Manager)是將多個原型對象存儲在一個集合中供客戶端使用,它是一個專門負責管理克隆對象的工廠,其中定義了一個集合用於存儲原型對象,如果需要某個原型對象的一個克隆,可以在集合中找到該原型對象並克隆一個新對象。在原型管理器中針對抽象原型類進行編程,便於擴展。
還是應用上面的例子,現在加入B公司:
[Serializable] public class SurveyPaperModel { public string FirstName { get; set; } public string LastName { get; set; } } [Serializable] public abstract class SuperSurveyPaper { public string Name { get; set; } public SurveyPaperModel Content { get; set; } public abstract SuperSurveyPaper Clone(); } [Serializable] public class CompanyASurveyPaper : SuperSurveyPaper { public override SuperSurveyPaper Clone() { MemoryStream memoryStream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memoryStream, this); memoryStream.Position = 0; return formatter.Deserialize(memoryStream) as SuperSurveyPaper; } } [Serializable] public class CompanyBSurveyPaper : SuperSurveyPaper { public override SuperSurveyPaper Clone() { MemoryStream memoryStream = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memoryStream, this); memoryStream.Position = 0; return formatter.Deserialize(memoryStream) as SuperSurveyPaper; } } public class PototypeManager { static IDictionary<string, SuperSurveyPaper> superSurveyPapers = new Dictionary<string, SuperSurveyPaper>(); static PototypeManager() { SuperSurveyPaper companyASurveyPaper = new CompanyASurveyPaper(); companyASurveyPaper.Name = "Company A"; companyASurveyPaper.Content= new SurveyPaperModel {FirstName="Michael",LastName="Du"}; SuperSurveyPaper companyBSurveyPaper = new CompanyBSurveyPaper(); companyBSurveyPaper.Name = "Company B"; companyBSurveyPaper.Content = new SurveyPaperModel { FirstName = "Kevin", LastName = "Durant" }; superSurveyPapers.Add("CompanyA", companyASurveyPaper); superSurveyPapers.Add("CompanyB", companyBSurveyPaper); } private PototypeManager (){} public SuperSurveyPaper GetSuperPaper(string key){ return superSurveyPapers[key].Clone(); } public void RegisterSurveyPaper(string key, SuperSurveyPaper ssp){ superSurveyPapers.Add(key, ssp); } public static PototypeManager Instance { get{ return PototypeManagerInitializer.instance;} } private static class PototypeManagerInitializer { public static readonly PototypeManager instance=new PototypeManager(); } }
這裡的PototypeManager類使用了一個Singleton模式創建出來,在靜態構造里初始化了原型對象,並將其註冊在一個字典中,這個在項目中數據是從資料庫中直接讀取的。這個管理類還暴露了一個註冊原型實例的方法,便於擴展和動態給管理器增加原型對象。在獲取Clone對象的方法中直接將原型對象的一個Copy返回給客戶程式。確保客戶端得到的對象是一個全新的對象。
客戶端調用代碼:
static void Main(string[] args) { PototypeInstance.SuperSurveyPaper pototype1, pototype2, pototype3, pototype4; pototype1 = PototypeManager.Instance.GetSuperPaper("CompanyA"); pototype2 = PototypeManager.Instance.GetSuperPaper("CompanyA"); Console.WriteLine("I'm old Name: " + pototype1.Name); Console.WriteLine("I'm old Content: " + pototype1.Content.FirstName); Console.WriteLine("I'm old Content: " + pototype1.Content.LastName); Console.WriteLine("I'm Clone Name: " + pototype2.Name); Console.WriteLine("I'm Clone Content: " + pototype2.Content.FirstName); Console.WriteLine("I'm Clone Content: " + pototype2.Content.LastName); Console.WriteLine("pototype1==pototype2:" + pototype2.Equals(pototype1)); Console.WriteLine("======================== ==========="); pototype3 = PototypeManager.Instance.GetSuperPaper("CompanyB"); pototype4 = PototypeManager.Instance.GetSuperPaper("CompanyB"); Console.WriteLine("I'm old Name: " + pototype3.Name); Console.WriteLine("I'm old Content: " + pototype3.Content.FirstName); Console.WriteLine("I'm old Content: " + pototype3.Content.LastName); Console.WriteLine("I'm Clone Name: " + pototype4.Name); Console.WriteLine("I'm Clone Content: " + pototype4.Content.FirstName); Console.WriteLine("I'm Clone Content: " + pototype4.Content.LastName); Console.WriteLine("pototype3==pototype4:" + pototype4.Equals(pototype3)); Console.ReadKey(); }
輸出結果:
模擬ctrl+c,ctrl+v
使用原型模式的“自我”複製能力,我們可以很容易的實現,創建副本和撤銷副本的功能, 在控制臺中我們輸入c 替代ctrl+c,輸入:z 替代ctrl+z 來模擬這個拷貝和撤銷的過程,首先我們創建一個原型對象,每次按C的時候使用最後clone出來的對象再克隆新的對象,並把這些對象依次保存在一個list中,當按Z的時候我們依次在list中移除最後加入的對象直到起初創建的原型對象為止, 簡單的客戶端代碼實現如下:
static List<PototypeInstance.SuperSurveyPaper> _list = new List<PototypeInstance.SuperSurveyPaper>(); static List<SurveyPaperModel> _listModel = new List<SurveyPaperModel> { new SurveyPaperModel{FirstName="Terry",LastName="Go"}, new SurveyPaperModel{FirstName="Ke",LastName="Be"}, new SurveyPaperModel{FirstName="Lebron",LastName="Jimes"}, new SurveyPaperModel{FirstName="Steve",LastName="Jo"}, new SurveyPaperModel{FirstName="Stive",LastName="Kurry"}, new SurveyPaperModel{FirstName="Henry",LastName="He"}, new SurveyPaperModel{FirstName="Kevin",LastName="Druant"}, new SurveyPaperModel{FirstName="Blue",LastName="Jhon"}, new SurveyPaperModel{FirstName="Jerry",LastName="Ma"}, new SurveyPaperModel{FirstName="Fred",LastName="Gao"}, }; static void Main(string[] args) { PototypeInstance.SuperSurveyPaper pototype1; pototype1 = PototypeManager.Instance.GetSuperPaper("CompanyB"); Console.WriteLine("I'm old Name: " + pototype1.Name); Console.WriteLine("I'm old Content: " + pototype1.Content.FirstName); Console.WriteLine("I'm old Content: " + pototype1.Content.LastName); _list.Add(pototype1); while (true) { var key = Console.ReadKey(); switch (key.Key) { case ConsoleKey.C: var pototypeLastInstance = _list.Last<PototypeInstance.SuperSurveyPaper>(); var cloneFromPototypeLastInstance = pototypeLastInstance.Clone(); cloneFromPototypeLastInstance.Name = "Version " + _list.Count; Random rd = new Random(); cloneFromPototypeLastInstance.Content = _listModel[rd.Next(0, _listModel.Count)]; _list.Add(cloneFromPototypeLastInstance); PrintList(_list); break; case ConsoleKey.Z: if (_list.Count > 1) _list.RemoveAt(_list.Count - 1); PrintList(_list); break; case ConsoleKey.Q: return; } } Console.ReadKey(); } static void PrintList(List<PototypeInstance.SuperSurveyPaper> list) { Console.WriteLine("========="); var pototpe = list.Last(); Console.WriteLine("History:" + pototpe.Name); Console.WriteLine("I'm Firstname: " + pototpe.Content.FirstName); Console.WriteLine("I'm LastName: " + pototpe.Content.LastName); }
客戶端輸出:
好了,到這裡設計模式的創建型模式就全部討論完了。下麵接著討論結構型模式。