如何使用C#語言實現原型模式

Liuwei-Sunny發表於2012-10-19

原型模式:使用原型例項指定待建立物件的型別,並且通過複製這個原型來建立新的物件。

 

        《西遊記》中,孫悟空可以根據自己的形狀複製(克隆)出多個身外身,如圖1所示,這種技巧在物件導向軟體設計領域被稱之為原型模式,孫悟空被稱之為原型物件。原型模式通過複製一個原型物件得到多個與原型物件一模一樣的新物件。

 

圖1 孫悟空複製身外身

        原型模式結構如圖2所示:

 

圖2 原型模式結構圖

       實現原型模式的關鍵在於如何實現克隆方法,不同的程式語言提供了不同的克隆方法實現機制,下面介紹兩種在C#語言中常用的克隆實現方法。

 

       1. 通用實現方法

      通用的克隆實現方法是在具體原型類的克隆方法中例項化一個與自身型別相同的物件並將其返回,並將相關的引數傳入新建立的物件中,保證它們的成員變數相同。示意程式碼如下:

abstract class Prototype
{
    public abstract Prototype Clone();
}

class ConcretePrototype : Prototype
{
    private string attr; //成員變數

    public string Attr
    {
        get { return attr; }
        set { attr = value; }
    }

    //克隆方法
    public override Prototype Clone()
    {
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.Attr = attr;
        return prototype;
    }
 }

       在客戶類中只需要建立一個ConcretePrototype物件作為原型物件,然後呼叫其Clone()方法即可得到對應的克隆物件,如下程式碼片段所示:

……
ConcretePrototype prototype = new ConcretePrototype();
ConcretePrototype copy = (ConcretePrototype)prototype.Clone();
……

       此方法是原型模式的通用實現,它與程式語言本身的特性無關,除C#外,其他物件導向程式語言也可以使用這種形式來實現對原型的克隆。

       在原型模式的通用實現方法中,可通過手工編寫Clone()方法來實現淺克隆和深克隆。對於引用型別的物件,可以在Clone()方法中通過賦值的方式來實現複製,這是一種淺克隆實現方案;如果在Clone()方法中通過建立一個全新的成員物件來實現複製,則是一種深克隆實現方案。C#語言中的字串(string/String)物件存在特殊性,只要兩個字串的內容相同,無論是直接賦值還是建立新物件,它們在記憶體中始終只有一份。如需進一步瞭解,大家可查閱“C#字串駐留機制”相關資料。

       2. C#中的MemberwiseClone()方法和ICloneable介面

       在C#語言中,提供了一個MemberwiseClone()方法用於實現淺克隆,該方法使用起來很方便,直接呼叫一個已有物件的MemberwiseClone()方法即可實現克隆。如下程式碼所示:

//成員類
class Member
{

}

class ConcretePrototypeA
{
   private Member member; //成員物件

    public Member Member
   {
      get { return member; }
      set { member = value; }
   }

    //克隆方法
    public ConcretePrototypeA Clone()
   {
      return (ConcretePrototypeA)this.MemberwiseClone(); //淺克隆
    }
}

       在客戶類中可以直接呼叫原型物件的Clone()方法來建立新的物件,如下程式碼片段所示:

……
ConcretePrototypeA prototype, copy;
prototype = new ConcretePrototypeA();
copy = prototype.Clone();
Console.WriteLine(prototype == copy);
Console.WriteLine(prototype.Member == copy.Member);
……

      在上述客戶類程式碼片段中,輸出語句“Console.WriteLine(prototype == copy);”的輸出結果為“False”,輸出語句“Console.WriteLine(prototype.Member == copy.Member);”的輸出結果為“True”,表明此處的克隆方法為淺克隆。

       除了MemberwiseClone()方法,在C#語言中還提供了一個ICloneable介面,它也可以用來建立當前物件的拷貝,其程式碼如下:

public interface ICloneable
{
    object Clone();
}

      ICloneable介面充當了抽象原型類的角色,具體原型類通常作為實現該介面的子類,如下程式碼所示:

class ConcretePrototypeB : ICloneable //實現ICloneable介面
{
    private Member member;

    public Member Member
    {
        get { return member; }
        set { member = value; }
    }

    //實現深克隆
    public object Clone()
    {
        ConcretePrototypeB copy = (ConcretePrototypeB)this.MemberwiseClone();
        Member newMember = new Member();
        copy.Member = newMember;
        return copy;
    }
}

      客戶類程式碼片段如下:

……
ConcretePrototypeB prototype, copy;
prototype = new ConcretePrototypeB();
copy = (ConcretePrototypeB) prototype.Clone();
Console.WriteLine(prototype == copy);
Console.WriteLine(prototype.Member == copy.Member);
……

      在此客戶類程式碼片段中,輸出語句“Console.WriteLine(prototype == copy);”的輸出結果為“False”,輸出語句“Console.WriteLine(prototype.Member == copy.Member);”的輸出結果也為“False”,表明此處的克隆方法為深克隆。

      在實現ICloneable介面時,通常提供的是除MemberwiseClone()以外的深克隆方法。除了通過直接建立新的成員物件來手工實現深克隆外,還可以通過反射、序列化等方式來實現深克隆,在使用序列化實現時要求所有被引用的物件都必須是可序列化的(Serializable)

      下面介紹一下如何使用C#的序列化機制來實現深克隆,使用序列化實現深克隆包含兩個步驟。

      首先必須將具體原型類(ConcretePrototype)和成員類(Member)標記為可序列化(Serializable),如下所示:

[Serializable]
class ConcretePrototype
{
    private Member member;    
    ……
}

[Serializable]
class Member
{
    ……
}

       然後將具體原型類(ConcretePrototype)的Clone()方法修改如下:

//使用序列化方式實現深克隆
public ConcretePrototype Clone()
{
    ConcretePrototype clone = null;
    FileStream fs = new FileStream("Temp.dat", FileMode.Create);
    BinaryFormatter formatter = new BinaryFormatter();
    try
    {
        formatter.Serialize(fs, this);  //序列化
    }
    catch (SerializationException e)
    {
        Console.WriteLine("Failed to serialize. Reason: " + e.Message);
        throw;
    }
    finally
    {
        fs.Close();
    }
 
    FileStream fs1 = new FileStream("Temp.dat", FileMode.Open);
    BinaryFormatter formatter1 = new BinaryFormatter();
    try
    {
        clone = (ConcretePrototype)formatter1.Deserialize(fs1);  //反序列化
    }
    catch (SerializationException e)
    {
        Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
        throw;
    }
    finally
    {
        fs1.Close();
    }
        return clone;
}

       上述程式碼的Clone()方法中,在成功複製原型物件的同時成員物件也被複制,實現了深克隆。

       在上述深克隆實現程式碼中,通過使用FileStream類和BinaryFormatter類可實現物件的序列化和反序列化操作,首先使用序列化將當前物件寫入流中,然後再使用反序列化從流中獲取物件。由於在序列化時一個物件的成員物件將伴隨該物件一起被寫入流中,在反序列化時將得到一個包含成員物件的新物件,因此可採用序列化和反序列化聯用來實現深克隆。

【作者:劉偉  http://blog.csdn.net/lovelion

相關文章