c#中原型模式詳解

2023-11-07 12:01:29

基礎介紹:

  具體可分為2個角色:

    Prototype(原型類):宣告一個Clone自身的介面;

    ConcretePrototype(具體原型類):,實現一個Clone自身的操作。

  在原型模式中,Prototype通常提供一個包含Clone方法的介面,具體的原型ConcretePrototype使用Clone方法完成物件的建立。

 

  本質:通過拷貝這些原型物件建立新的物件。

  根據其本質可以理解,原型本身就是通過一個自身的Clone方法來進行自我複製,從而產生新的物件。

  比如,孫猴子吹猴毛變化多個克隆體時,就是用了原型模式,通過對自身的自我複製從而生產出N個分身。

  所以從本質出發,想要實現這個功能,可以引出兩個概念:其一就是淺層複製,再則就是深層複製

  淺層複製:通過this.MemberWiseClone(),對範例的值型別進行拷貝(包含string型別),對參照型別只拷貝了參照。淺拷貝只對值型別成員進行復制,對於參照型別,只是複製了其參照,並不複製其物件。

  深層複製需要通過反射和序列化來實現。

 

應用場景:

  物件在建立(new)時,消耗資源過多繁瑣耗時。

  本質就是在物件的建構函式中有耗時長或者佔用系統資源多的情況,

  使用原型模式進行復制物件時,可以省去這些耗時耗力的操作,直接獲得物件的具體範例。

  最常見的使用場景之一就是物件歷史節點的儲存,比如在對物件進行操作一次後,進行一次複製儲存當前狀態(恢復到某一歷史狀態),可實現復原操作。

建立方式:

  1. 原型類----用來規範具體原型

     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     }
    View Code

    通過上述程式碼可以看出,為了更好的展示原型類的特性,原型類中宣告了值型別和參照型別來展示各自的變化。

  2. 具體原型類

     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     }
    View Code

    通過MemberwiseClone方法實現淺拷貝,即複製值型別屬性生成新的,而參照型別的屬性只複製其參照,並沒有生成新的。

  3. 使用者端呼叫

     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     }
    View Code

    上述程式碼中,首先建立了一個concretePrototype原型物件,然後給字串型別的strMessage賦值「AAAAAAAAA」。 然後給參照型別的keyValuePairs字典新增key=1和key=2,值分別是A和B。

    通過Clone()方法進行原型物件的複製操作,生成新物件concretePrototype2。

    修改新物件中的strMessage屬性和keyValuePairs字典中key=1的值為「A1」。

    通過列印出的內容可以看出新物件中的strMessage值修改並不會影響原型物件中的內容,而參照型別keyValuePairs則發生了改變。

    通過這個範例可以看出淺複製,對值型別進行全盤拷貝,對參照型別只拷貝了參照地址。

  4. 修改上述範例,將淺複製改為深複製

     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     }
    View Code

    上述範例通過序列化進行深複製,當然也可以使用反射等技術進行深複製。

    執行後可以看出,深複製後參照型別也會生成一個新的地址。

總結:

  原型模式就是對物件進行復制操作,而避免重複進行初始化操作,生產多個克隆物件。