2014-01-14 95 views
28

在類層次結構中實現ICloneable的正確方法是什麼?假設我有一個抽象類DrawingObject。另一個抽象類RectangularObject繼承自DrawingObject。然後有多個具體類,如ShapeTextCircle等,這些都從RectangularObject繼承。我想在DrawingObject上執行ICloneable,然後將其沿着層次結構進行下載,複製每個級別的可用屬性並在下一級調用父級的Clone實現ICloneable的正確方法

但問題是,由於前兩個類是抽象的,我不能在Clone()方法中創建它們的對象。因此我必須在每個具體的類中複製屬性複製過程。或者,還有更好的方法?

+5

你爲什麼要實現ICloneable? [從MSDN](http://msdn.microsoft.com/en-us/library/system.icloneable%28v=vs.110%29.aspx)「*因爲克隆的調用者不能依賴執行可預測的方法克隆操作,我們推薦ICloneable不能在公共API中實現。*「你有充分理由實現ICloneable接口嗎? –

+1

@ScottChamberlain:這可能適用於公共API,但我正在使用自己的代碼實現它。我可以直接訪問所有涉及的類。我必須試着想一個適當的面向對象的方式。 – dotNET

+1

@ElliotTereschuk:我想過這樣做,但它聞起來像一個非OOP(事實上反OOP)的方法。 – dotNET

回答

1

在我看來,最明顯的方法是應用BinaryFormatterMemoryStream二進制序列化。

MSDN thread about deep cloning in C#建議上述方法。

+1

雖然這在某些情況下有幫助,但不利的一面是你可能對克隆的內容有較少的控制。也許你不想克隆整個可到達的對象圖,但實際上保持對一些(但不是全部)「中心對象」的引用完好無損。例如一個繪圖元素可能應該克隆它的「子項目」,而不是它的所有者文檔。在實踐中,我發現既不是嚴格意義上的淺層次,也不是嚴格意義上的深層次克隆,通常這是介於兩者之間的事情。 –

+0

@ O.R.Mapper這種方法的主要目標是它的例程實際上需要5個字符串的代碼。自定義克隆的執行與提及每個單獨的字段和(或)屬性 –

+0

同意@ O.R.Mapper在這裏。 – dotNET

1

給你的基類一個受保護的和可重寫的CreateClone()方法,該方法創建一個新的(空的)當前類的實例。然後讓基類的Clone()方法調用該方法以多態方式實例化新實例,然後基類可以將其字段值複製到該實例。

派生的非抽象類可以重寫CreateClone()方法來實例化適當的類,並且引入新的領域的所有派生類可以調用的Clone()繼承的版本後,覆蓋Clone()其附加字段的值複製到新的實例。

public CloneableBase : ICloneable 
{ 
    protected abstract CloneableBase CreateClone(); 

    public virtual object Clone() 
    { 
     CloneableBase clone = CreateClone(); 
     clone.MyFirstProperty = this.MyFirstProperty; 
     return clone; 
    } 

    public int MyFirstProperty { get; set; } 
} 

public class CloneableChild : CloneableBase 
{ 
    protected override CloneableBase CreateClone() 
    { 
     return new CloneableChild(); 
    } 

    public override object Clone() 
    { 
     CloneableChild clone = (CloneableChild)base.Clone(); 
     clone.MySecondProperty = this.MySecondProperty; 
     return clone; 
    } 

    public int MySecondProperty { get; set; } 
} 

如果你想跳過(至少在默認情況下),第一首要一步,你也可以假設一個默認的構造函數簽名(如無參數),並嘗試使用與反思該構造函數簽名來實例化一個克隆實例。像這樣,只有構造函數與默認簽名不匹配的類纔會覆蓋CreateClone()

一個非常簡單的版本,默認CreateClone()實施看起來是這樣的:

protected virtual CloneableBase CreateClone() 
{ 
    return (CloneableBase)Activator.CreateInstance(GetType()); 
} 
+0

等一下,基類是抽象的。如何在'CreateClone()'方法中創建一個空對象? – dotNET

+0

@dotNET:或者,'CreateClone()'方法在基類中是抽象的,每個類都必須覆蓋它。或者,'CreateClone()'方法使用反射實例化GetType()返回的任何內容,並且失敗的類必須重寫'CreateClone()',其他類可以依賴於默認實現。 –

+0

@dotNET:我已經添加了一些示例代碼來說明它是如何工作的。 –

0

這裏是我已經躺在附近,我幾年前寫了一些示例代碼複製粘貼。

這幾天,我避免了需要克隆支持的設計;我發現大多數這樣的設計有點片面。相反,我廣泛使用不可變類來避免首先克隆的需要。

話雖如此,這裏的樣本克隆模式:

using System; 
using System.IO; 
using System.Diagnostics; 

/* 

This code demonstrates a cloning pattern that you can use for class hierarchies. 

The abstract base class specifies an abstract Clone() method which must be implemented by all derived classes. 
Every class except the abstract base class must have a protected copy constructor. 

This protected copy constructor will: 

(1) call the base class' copy constructor, and 
(2) set any new fields introduced in the derived class. 

This code also demonstrates an implementation of Equals() and CopyFrom(). 

*/ 

namespace CloningPattern 
{ 
    //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

    static class Program 
    { 
     static void Main() 
     { 
      Derived2 test = new Derived2() 
      { 
       IntValue = 1, 
       StringValue = "s", 
       DoubleValue = 2, 
       ShortValue = 3 
      }; 

      Derived2 copy = Clone(test); 
      Console.WriteLine(copy); 
     } 

     static Derived2 Clone(AbstractBase item) 
     { 
      AbstractBase abstractBase = (AbstractBase) item.Clone(); 
      Derived2 result = abstractBase as Derived2; 
      Debug.Assert(result != null); 
      return result; 
     } 
    } 

    //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

    public abstract class AbstractBase: ICloneable 
    { 
     // Sample data field. 

     public int IntValue { get; set; } 

     // Canonical way of providing a Clone() operation 
     // (except that this is abstract rather than virtual, since this class 
     // is itself abstract). 

     public abstract object Clone(); 

     // Default constructor. 

     protected AbstractBase(){} 

     // Copy constructor. 

     protected AbstractBase(AbstractBase other) 
     { 
      if (other == null) 
      { 
       throw new ArgumentNullException("other"); 
      } 

      this.copyFrom(other); 
     } 

     // Copy from another instance over the top of an already existing instance. 

     public virtual void CopyFrom(AbstractBase other) 
     { 
      if (other == null) 
      { 
       throw new ArgumentNullException("other"); 
      } 

      this.copyFrom(other); 
     } 

     // Equality check. 

     public override bool Equals(object obj) 
     { 
      if (obj == null) 
      { 
       return false; 
      } 

      if (object.ReferenceEquals(this, obj)) 
      { 
       return true; 
      } 

      if (this.GetType() != obj.GetType()) 
      { 
       return false; 
      } 

      AbstractBase other = (AbstractBase)obj; 

      return (this.IntValue == other.IntValue); 
     } 

     // Get hash code. 

     public override int GetHashCode() 
     { 
      return this.IntValue.GetHashCode(); 
     } 

     // ToString() for debug purposes. 

     public override string ToString() 
     { 
      return "IntValue = " + IntValue; 
     } 

     // Implement copying fields in a private non-virtual method, called from more than one place. 

     private void copyFrom(AbstractBase other) // 'other' cannot be null, so no check for nullness is made. 
     { 
      this.IntValue = other.IntValue; 
     } 
    } 

    //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

    public abstract class AbstractDerived: AbstractBase 
    { 
     // Sample data field. 

     public short ShortValue{ get; set; } 

     // Default constructor. 

     protected AbstractDerived(){} 

     // Copy constructor. 

     protected AbstractDerived(AbstractDerived other): base(other) 
     { 
      this.copyFrom(other); 
     } 

     // Copy from another instance over the top of an already existing instance. 

     public override void CopyFrom(AbstractBase other) 
     { 
      base.CopyFrom(other); 
      this.copyFrom(other as AbstractDerived); 
     } 

     // Comparison. 

     public override bool Equals(object obj) 
     { 
      if (object.ReferenceEquals(this, obj)) 
      { 
       return true; 
      } 

      if (!base.Equals(obj)) 
      { 
       return false; 
      } 

      AbstractDerived other = (AbstractDerived)obj; // This must succeed because if the types are different, base.Equals() returns false. 

      return (this.IntValue == other.IntValue); 
     } 

     // Get hash code. 

     public override int GetHashCode() 
     { 
      // "Standard" way of combining hash codes from subfields. 

      int hash = 17; 

      hash = hash * 23 + base.GetHashCode(); 
      hash = hash * 23 + this.ShortValue.GetHashCode(); 

      return hash; 
     } 

     // ToString() for debug purposes. 

     public override string ToString() 
     { 
      return base.ToString() + ", ShortValue = " + ShortValue; 
     } 

     // This abstract class doesn't need to implement Clone() because no instances of it 
     // can ever be created, on account of it being abstract and all that. 
     // If you COULD, it would look like this (but you can't so this won't compile): 

     // public override object Clone() 
     // { 
     //  return new AbstractDerived(this); 
     // } 

     // Implement copying fields in a private non-virtual method, called from more than one place. 

     private void copyFrom(AbstractDerived other) // Other could be null, so check for nullness. 
     { 
      if (other != null) 
      { 
       this.ShortValue = other.ShortValue; 
      } 
     } 
    } 

    //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

    public class Derived1: AbstractDerived 
    { 
     // Must declare a default constructor. 

     public Derived1(){} 

     // Sample data field. 

     public string StringValue{ get; set; } 

     // Implement Clone() by simply using this class' copy constructor. 

     public override object Clone() 
     { 
      return new Derived1(this); 
     } 

     // Copy from another instance over the top of an already existing instance. 

     public override void CopyFrom(AbstractBase other) 
     { 
      base.CopyFrom(other); 
      this.copyFrom(other as Derived1); 
     } 

     // Equality check. 

     public override bool Equals(object obj) 
     { 
      if (object.ReferenceEquals(this, obj)) 
      { 
       return true; 
      } 

      if (!base.Equals(obj)) 
      { 
       return false; 
      } 

      Derived1 other = (Derived1)obj; // This must succeed because if the types are different, base.Equals() returns false. 

      return (this.StringValue == other.StringValue); 
     } 

     // Get hash code. 

     public override int GetHashCode() 
     { 
      // "Standard" way of combining hash codes from subfields. 

      int hash = 17; 

      hash = hash * 23 + base.GetHashCode(); 
      hash = hash * 23 + this.StringValue.GetHashCode(); 

      return hash; 
     } 

     // ToString() for debug purposes. 

     public override string ToString() 
     { 
      return base.ToString() + ", StringValue = " + StringValue; 
     } 

     // Protected copy constructor. Used to implement Clone(). 
     // Also called by a derived class' copy constructor. 

     protected Derived1(Derived1 other): base(other) 
     { 
      this.copyFrom(other); 
     } 

     // Implement copying fields in a private non-virtual method, called from more than one place. 

     private void copyFrom(Derived1 other) // Other could be null, so check for nullness. 
     { 
      if (other != null) 
      { 
       this.StringValue = other.StringValue; 
      } 
     } 
    } 

    //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

    public class Derived2: Derived1 
    { 
     // Must declare a default constructor. 

     public Derived2(){} 

     // Sample data field. 

     public double DoubleValue{ get; set; } 

     // Implement Clone() by simply using this class' copy constructor. 

     public override object Clone() 
     { 
      return new Derived2(this); 
     } 

     // Copy from another instance over the top of an already existing instance. 

     public override void CopyFrom(AbstractBase other) 
     { 
      base.CopyFrom(other); 
      this.copyFrom(other as Derived2); 
     } 

     // Equality check. 

     public override bool Equals(object obj) 
     { 
      if (object.ReferenceEquals(this, obj)) 
      { 
       return true; 
      } 

      if (!base.Equals(obj)) 
      { 
       return false; 
      } 

      Derived2 other = (Derived2)obj; // This must succeed because if the types are different, base.Equals() returns false. 

      return (this.DoubleValue == other.DoubleValue); 
     } 

     // Get hash code. 

     public override int GetHashCode() 
     { 
      // "Standard" way of combining hash codes from subfields. 

      int hash = 17; 

      hash = hash * 23 + base.GetHashCode(); 
      hash = hash * 23 + this.DoubleValue.GetHashCode(); 

      return hash; 
     } 

     // ToString() for debug purposes. 

     public override string ToString() 
     { 
      return base.ToString() + ", DoubleValue = " + DoubleValue; 
     } 

     // Protected copy constructor. Used to implement Clone(). 
     // Also called by a derived class' copy constructor. 

     protected Derived2(Derived2 other): base(other) 
     { 
      // Canonical implementation: use ":base(other)" to copy all 
      // the base fields (which recursively applies all the way to the ultimate base) 
      // and then explicitly copy any of this class' fields here: 

      this.copyFrom(other); 
     } 

     // Implement copying fields in a private non-virtual method, called from more than one place. 

     private void copyFrom(Derived2 other) // Other could be null, so check for nullness. 
     { 
      if (other != null) 
      { 
       this.DoubleValue = other.DoubleValue; 
      } 
     } 
    } 
} 
33

您可以輕鬆創建一個膚淺的克隆與object「保護法MemberwiseClone秒。

例子:

public abstract class AbstractCloneable : ICloneable 
    { 
     public object Clone() 
     { 
     return this.MemberwiseClone(); 
     } 
    } 

如果您不需要像深拷貝任何東西,你會不會做的子類東西。

MemberwiseClone方法通過創建一個新對象,然後將當前對象的非靜態字段複製到新對象來創建一個淺表副本。如果某個字段是值類型,則會執行該字段的逐位副本。如果一個字段是一個引用類型,則引用被複制,但引用的對象不是;因此,原始對象及其克隆涉及同一個對象。

如果您在克隆邏輯需要更多的智慧,你可以添加一個虛擬的方法來處理引用:

public abstract class AbstractCloneable : ICloneable 
    { 
     public object Clone() 
     { 
     var clone = (AbstractCloneable) this.MemberwiseClone(); 
     HandleCloned(clone); 
     return clone; 
     } 

     protected virtual HandleCloned(AbstractCloneable clone) 
     { 
     //Nothing particular in the base class, but maybe usefull for childs. 
     //Not abstract so childs may not implement this if they don't need to. 
     } 

    } 


    public class ConcreteCloneable : AbstractCloneable 
    { 
     protected override HandleCloned(AbstractCloneable clone) 
     { 
      //Get wathever magic a base class could have implemented. 
      base.HandleCloned(clone); 

      //Clone is of the current type. 
      ConcreteCloneable obj = (ConcreteCloneable) clone; 

      //Here you have a superficial copy of "this". You can do wathever 
      //specific task you need to do. 
      //e.g.: 
      obj.SomeReferencedPropertie = this.SomeReferencedPropertie.Clone(); 
     } 
    } 
+0

當然,你可以隨時調用'base。HandleCloned'來調用父類的邏輯。我認爲這是解決方案涉及較少的代碼編寫。 'MemberwiseClone'處理對象創建的魔力。 – Johnny5

1

最起碼你只讓具體類處理的克隆,和抽象類有protected副本構造函數。現在,在此之上,你希望能夠採取的DrawingObject變量和克隆這樣的:

class Program 
{ 
    static void Main(string[] args) 
    { 
     DrawingObject obj1=new Circle(Color.Black, 10); 
     DrawingObject obj2=obj1.Clone(); 
    } 
} 

你可能會認爲這是作弊,但我會用擴展方法和反思:

public abstract class DrawingObject 
{ 
    public abstract void Draw(); 
    public Color Color { get; set; } 
    protected DrawingObject(DrawingObject other) 
    { 
     this.Color=other.Color; 
    } 
    protected DrawingObject(Color color) { this.Color=color; } 
} 

public abstract class RectangularObject : DrawingObject 
{ 
    public int Width { get; set; } 
    public int Height { get; set; } 
    protected RectangularObject(RectangularObject other) 
     : base(other) 
    { 
     Height=other.Height; 
     Width=other.Width; 
    } 
    protected RectangularObject(Color color, int width, int height) 
     : base(color) 
    { 
     this.Width=width; 
     this.Height=height; 
    } 
} 

public class Circle : RectangularObject, ICloneable 
{ 
    public int Diameter { get; set; } 
    public override void Draw() 
    { 
    } 
    public Circle(Circle other) 
     : base(other) 
    { 
     this.Diameter=other.Diameter; 
    } 
    public Circle(Color color, int diameter) 
     : base(color, diameter, diameter) 
    { 
     Diameter=diameter; 
    } 

    #region ICloneable Members 
    public Circle Clone() { return new Circle(this); } 
    object ICloneable.Clone() 
    { 
     return Clone(); 
    } 
    #endregion 

} 

public class Square : RectangularObject, ICloneable 
{ 
    public int Side { get; set; } 
    public override void Draw() 
    { 
    } 
    public Square(Square other) 
     : base(other) 
    { 
     this.Side=other.Side; 
    } 
    public Square(Color color, int side) 
     : base(color, side, side) 
    { 
     this.Side=side; 
    } 

    #region ICloneable Members 
    public Square Clone() { return new Square(this); } 
    object ICloneable.Clone() 
    { 
     return Clone(); 
    } 
    #endregion 

} 

public static class Factory 
{ 
    public static T Clone<T>(this T other) where T : DrawingObject 
    { 
     Type t = other.GetType(); 
     ConstructorInfo ctor=t.GetConstructor(new Type[] { t }); 
     if (ctor!=null) 
     { 
      ctor.Invoke(new object[] { other }); 
     } 
     return default(T); 
    } 
} 

編輯1

如果您對速度感到厭倦(每次都進行反射),您可以a)將構造函數緩存在靜態字典中。

public static class Factory 
{ 
    public static T Clone<T>(this T other) where T : DrawingObject 
    { 
     return Dynamic<T>.CopyCtor(other); 
    } 
} 

public static class Dynamic<T> where T : DrawingObject 
{ 
    static Dictionary<Type, Func<T, T>> cache = new Dictionary<Type,Func<T,T>>(); 

    public static T CopyCtor(T other) 
    { 
     Type t=other.GetType(); 
     if (!cache.ContainsKey(t)) 
     { 
      var ctor=t.GetConstructor(new Type[] { t }); 
      cache.Add(t, (x) => ctor.Invoke(new object[] { x }) as T); 
     } 
     return cache[t](other); 
    } 
}