具體可分為2個角色:
Prototype(原型類):宣告一個Clone自身的介面;
ConcretePrototype(具體原型類):,實現一個Clone自身的操作。
在原型模式中,Prototype通常提供一個包含Clone方法的介面,具體的原型ConcretePrototype使用Clone方法完成物件的建立。
本質:通過拷貝這些原型物件建立新的物件。
根據其本質可以理解,原型本身就是通過一個自身的Clone方法來進行自我複製,從而產生新的物件。
比如,孫猴子吹猴毛變化多個克隆體時,就是用了原型模式,通過對自身的自我複製從而生產出N個分身。
所以從本質出發,想要實現這個功能,可以引出兩個概念:其一就是淺層複製,再則就是深層複製。
淺層複製:通過this.MemberWiseClone(),對範例的值型別進行拷貝(包含string型別),對參照型別只拷貝了參照。淺拷貝只對值型別成員進行復制,對於參照型別,只是複製了其參照,並不複製其物件。
深層複製:需要通過反射和序列化來實現。
物件在建立(new)時,消耗資源過多或繁瑣耗時。
本質就是在物件的建構函式中有耗時長或者佔用系統資源多的情況,
使用原型模式進行復制物件時,可以省去這些耗時耗力的操作,直接獲得物件的具體範例。
最常見的使用場景之一就是物件歷史節點的儲存,比如在對物件進行操作一次後,進行一次複製儲存當前狀態(恢復到某一歷史狀態),可實現復原操作。
原型類----用來規範具體原型
1 /// <summary> 2 /// 原型類 3 /// </summary> 4 public abstract class Prototype 5 { 6 /// <summary> 7 /// 值型別 8 /// </summary> 9 public int Id { get; set; } 10 11 /// <summary> 12 /// 字串 13 /// </summary> 14 public string strMessage { get; set; } 15 16 /// <summary> 17 /// 參照型別 18 /// </summary> 19 public Dictionary<int, string> keyValuePairs = new Dictionary<int, string>() { }; 20 21 /// <summary> 22 /// 建構函式 23 /// </summary> 24 /// <param name="id"></param> 25 public Prototype(int id) 26 { 27 this.Id = id; 28 } 29 30 /// <summary> 31 /// 複製函數 32 /// </summary> 33 /// <returns></returns> 34 public abstract Prototype Clone(); 35 }
通過上述程式碼可以看出,為了更好的展示原型類的特性,原型類中宣告了值型別和參照型別來展示各自的變化。
具體原型類
1 /// <summary> 2 /// 建立具體原型 3 /// </summary> 4 public class ConcretePrototype : Prototype 5 { 6 public ConcretePrototype(int id) 7 : base(id) 8 { } 9 10 /// <summary> 11 /// 淺拷貝 12 /// </summary> 13 /// <returns></returns> 14 public override Prototype Clone() 15 { 16 // 呼叫MemberwiseClone方法實現的是淺拷貝,另外還有深拷貝 17 return (Prototype)this.MemberwiseClone(); 18 } 19 }
通過MemberwiseClone方法實現淺拷貝,即複製值型別屬性生成新的,而參照型別的屬性只複製其參照,並沒有生成新的。
使用者端呼叫
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ConcretePrototype concretePrototype = new ConcretePrototype(1); 6 concretePrototype.strMessage = "AAAAAAAAA"; 7 concretePrototype.keyValuePairs.Add(1, "A"); 8 concretePrototype.keyValuePairs.Add(2, "B"); 9 Console.WriteLine("id:{0}", concretePrototype.Id); 10 Console.WriteLine("strMessage:{0}", concretePrototype.strMessage); 11 Console.WriteLine("keyValuePairs:"); 12 foreach (KeyValuePair<int,string> item in concretePrototype.keyValuePairs) 13 { 14 Console.WriteLine("KEY:{0} Value:{1}", item.Key, item.Value); 15 } 16 17 Console.WriteLine("\r\n"); 18 19 ConcretePrototype concretePrototype2 = (ConcretePrototype)concretePrototype.Clone(); 20 concretePrototype2.strMessage = "BBBBBBBBB"; 21 concretePrototype2.keyValuePairs[1] = "A1"; 22 Console.WriteLine("id:{0}", concretePrototype2.Id); 23 Console.WriteLine("strMessage:{0}", concretePrototype2.strMessage); 24 Console.WriteLine("keyValuePairs:"); 25 foreach (KeyValuePair<int, string> item in concretePrototype2.keyValuePairs) 26 { 27 Console.WriteLine("KEY:{0} Value:{1}", item.Key, item.Value); 28 } 29 30 Console.WriteLine("\r\n"); 31 32 Console.WriteLine("id:{0}", concretePrototype.Id); 33 Console.WriteLine("strMessage:{0}", concretePrototype.strMessage); 34 Console.WriteLine("keyValuePairs:"); 35 foreach (KeyValuePair<int, string> item in concretePrototype.keyValuePairs) 36 { 37 Console.WriteLine("KEY:{0} Value:{1}", item.Key, item.Value); 38 } 39 Console.ReadKey(); 40 } 41 }
上述程式碼中,首先建立了一個concretePrototype原型物件,然後給字串型別的strMessage賦值「AAAAAAAAA」。 然後給參照型別的keyValuePairs字典新增key=1和key=2,值分別是A和B。
通過Clone()方法進行原型物件的複製操作,生成新物件concretePrototype2。
修改新物件中的strMessage屬性和keyValuePairs字典中key=1的值為「A1」。
通過列印出的內容可以看出新物件中的strMessage值修改並不會影響原型物件中的內容,而參照型別keyValuePairs則發生了改變。
通過這個範例可以看出淺複製,對值型別進行全盤拷貝,對參照型別只拷貝了參照地址。
修改上述範例,將淺複製改為深複製
1 /// <summary> 2 /// 原型類 3 /// </summary> 4 [Serializable] 5 public abstract class Prototype 6 { 7 ...... 8 } 9 10 /// <summary> 11 /// 建立具體原型 12 /// 如果是要通過序列化來進行深拷貝的話,要打上Serializable標籤 13 /// </summary> 14 [Serializable] 15 public class ConcretePrototype : Prototype 16 { 17 public ConcretePrototype(int id) 18 : base(id) 19 { } 20 21 /// <summary> 22 /// 深拷貝 23 /// </summary> 24 /// <returns>返回一個全新的Person物件</returns> 25 public override Prototype Clone() 26 { 27 //建立一個記憶體流 28 MemoryStream ms = new MemoryStream(); 29 //建立一個二進位制序列化物件 30 BinaryFormatter bf = new BinaryFormatter(); 31 //將當前物件序列化寫入ms記憶體流中 32 bf.Serialize(ms, this); 33 //設定流讀取的位置 34 ms.Position = 0; 35 //將流反序列化為Object物件 36 return bf.Deserialize(ms) as Prototype; 37 } 38 }
上述範例通過序列化進行深複製,當然也可以使用反射等技術進行深複製。
執行後可以看出,深複製後參照型別也會生成一個新的地址。
原型模式就是對物件進行復制操作,而避免重複進行初始化操作,生產多個克隆物件。