web-dev-qa-db-fra.com

Moyen approprié d'implémenter ICloneable

Quelle est la manière appropriée d'implémenter ICloneable dans une hiérarchie de classes? Disons que j'ai une classe abstraite DrawingObject. Une autre classe abstraite RectangularObject hérite de DrawingObject. Ensuite, il existe plusieurs classes concrètes telles que Shape, Text, Circle etc. qui héritent toutes de RectangularObject. Je souhaite implémenter ICloneable sur DrawingObject, puis le transférer dans la hiérarchie, en copiant les propriétés disponibles à chaque niveau et en appelant Clone du parent au niveau suivant.

Cependant, le problème est que, les deux premières classes étant abstraites, je ne peux pas créer leurs objets dans la méthode Clone(). Je dois donc dupliquer la procédure de copie de propriété dans chaque classe concrète. Ou y a-t-il un meilleur moyen?

41
dotNET

Vous pouvez facilement créer un clone superficiel avec la méthode protégée de objectMemberwiseClone .

Exemple:

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

Si vous n'avez pas besoin de copie profonde, vous n'aurez rien à faire dans les classes enfants.

La méthode MemberwiseClone crée une copie superficielle en créant un nouvel objet, puis en copiant les champs non statiques de l'objet en cours dans le nouvel objet. Si un champ est un type de valeur, une copie bit par bit du champ est effectuée. Si un champ est un type de référence, la référence est copiée mais l'objet référencé ne l'est pas. Par conséquent, l'objet d'origine et son clone font référence au même objet.

Si vous avez besoin de plus d'intelligence dans la logique de clonage, vous pouvez ajouter une méthode virtuelle pour gérer les références:

   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 useful for children.
         //Not abstract so children may not implement this if they don't need to.
      }
   }


   public class ConcreteCloneable : AbstractCloneable
   {
       protected override HandleCloned(AbstractCloneable clone)
       {
           //Get whathever 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 whathever 
           //specific task you need to do.
           //e.g.:
           obj.SomeReferencedProperty = this.SomeReferencedProperty.Clone();
       }
   }
48
Johnny5

Je pense avoir une amélioration par rapport à l'excellente réponse de @ johnny5. Définissez simplement les constructeurs de copie dans toutes les classes et dans la classe de base, utilisez la réflexion dans la méthode Clone pour rechercher le constructeur de copie et l'exécuter. Je pense que cela est légèrement plus propre, car vous n'avez pas besoin d'une pile de substitutions de clones de poignées ni de MemberwiseClone (), qui est simplement un instrument trop émoussé dans de nombreuses situations.

public abstract class AbstractCloneable : ICloneable
    {
        public int BaseValue { get; set; }
        protected AbstractCloneable()
        {
            BaseValue = 1;
        }
        protected AbstractCloneable(AbstractCloneable d)
        {
            BaseValue = d.BaseValue;
        }

        public object Clone()
        {
            var clone = ObjectSupport.CloneFromCopyConstructor(this);
            if(clone == null)throw new ApplicationException("Hey Dude, you didn't define a copy constructor");
            return clone;
        }

    }


    public class ConcreteCloneable : AbstractCloneable
    {
        public int DerivedValue { get; set; }
        public ConcreteCloneable()
        {
            DerivedValue = 2;
        }

        public ConcreteCloneable(ConcreteCloneable d)
            : base(d)
        {
            DerivedValue = d.DerivedValue;
        }
    }

    public class ObjectSupport
    {
        public static object CloneFromCopyConstructor(System.Object d)
        {
            if (d != null)
            {
                Type t = d.GetType();
                foreach (ConstructorInfo ci in t.GetConstructors())
                {
                    ParameterInfo[] pi = ci.GetParameters();
                    if (pi.Length == 1 && pi[0].ParameterType == t)
                    {
                        return ci.Invoke(new object[] { d });
                    }
                }
            }

            return null;
        }
    }

Enfin, permettez-moi de parler en faveur de ICloneable. Si vous utilisez cette interface, vous serez battu par la police de style car les directives de conception du .NET Framework interdisaient de l’appliquer car, pour citer ces instructions, "lorsqu’un objet implémentant un type avec ICloneable, vous ne saurez jamais vont obtenir. Cela rend l'interface inutile. " L'implication est que vous ne savez pas si vous obtenez une copie profonde ou superficielle. Eh bien, c'est simplement du sophisme. Cela implique-t-il que les constructeurs de copie ne doivent jamais être utilisés car "vous ne savez jamais ce que vous allez obtenir?" Bien sûr que non. Si vous ne savez pas ce que vous allez obtenir, c'est simplement un problème de conception de classe, pas d'interface.

3
AQuirky

Donnez à votre classe de base une méthode CreateClone() protégée et pouvant être remplacée, qui crée une nouvelle instance (vide) de la classe actuelle. Demandez ensuite à la méthode Clone() de la classe de base d'appeler cette méthode pour instancier de manière polymorphe une nouvelle instance, à laquelle la classe de base peut ensuite copier ses valeurs de champ.

Les classes dérivées non abstraites peuvent remplacer la méthode CreateClone() pour instancier la classe appropriée, et toutes les classes dérivées introduisant de nouveaux champs peuvent remplacer Clone() pour copier leurs valeurs de champ supplémentaires dans la nouvelle instance après avoir appelé la version héritée de 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; }
}

Si vous souhaitez ignorer la première étape de remplacement (au moins dans le cas par défaut), vous pouvez également supposer une signature de constructeur par défaut (par exemple, sans paramètre) et essayer d'instancier une instance de clone en utilisant cette signature de constructeur avec réflexion. De même, seules les classes dont les constructeurs ne correspondent pas à la signature par défaut devront remplacer CreateClone().

Une version très simple de cette implémentation par défaut de CreateClone() pourrait ressembler à ceci:

protected virtual CloneableBase CreateClone()
{
    return (CloneableBase)Activator.CreateInstance(GetType());
}
2
O. R. Mapper

À mon avis, le moyen le plus clair est d'appliquer la sérialisation binaire avec BinaryFormatter dans MemoryStream.

Il existe un thread MSDN sur le clonage profond en C # , dans lequel l'approche ci-dessus est suggérée.

1

Au minimum, vous ne laissez que des classes concrètes gérer le clonage et les classes abstraites ont des constructeurs de copie protected. Maintenant, en plus de cela, vous voulez pouvoir prendre une variable de DrawingObject et la cloner comme ceci:

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

Vous pourriez envisager cette triche, mais j'utiliserais des méthodes d'extension et de réflexion:

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);
    }
}

Modifier 1

Si vous êtes préoccupé par la vitesse (faisant une réflexion à chaque fois), vous pouvez: a) mettre le constructeur en cache dans un dictionnaire statique.

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);
    }
}
0
ja72