2014-01-14 100 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); 
    } 
}