现在阅读
Understanding and Implementing Prototype Pattern in C# – Implementing ICloneable Interface and understanding Shallow and Deep Copy
0

Understanding and Implementing Prototype Pattern in C# – Implementing ICloneable Interface and understanding Shallow and Deep Copy

由 ultracpy2018年1月26日

Introduction

This article talks about the prototype pattern, when should it be used. We will then see how the
prototype pattern can be implemented in C#. Also, this article will discuss about the shallow copy and
deep copy in C# and how can we implement ICloneable interface to implement prototype pattern in a .Net optimized
way.

Background

There are many situations in our applications where we want to take an object’s copy from the context
and then proceed with this copy with some independent set of operations. Prototype pattern is specifically
useful in such scenarios where we can simply copy an object from the current application context and then
proceed with it independent of the original object.

GoF defines prototype pattern as “Specify the kind of objects to create using a prototypical instance,
and create new objects by copying this prototype
.” so to visualize this pattern let us look at the class
diagram of this pattern.

In the above diagram the major players involved are:

  • Prototype: This is an interface or abstract class that defined the method to clone itself.
  • ConcretePrototype: This is the concrete class that will clone itself.
  • Client: The application object that need the cloned copy of the object.

Let us now try to see how we can implement this pattern in C#. 

Using the code

To illustrate this pattern, we need a scenario where we need to clone an existing object. For the
sake of simplicity I have taken a rudimentary and hypothetical example of a famous auto theft game.

Lets say we have a protagonist in the game. The protagonist has some statistics to define his
playing variables. Now whenever we need to save the game the clone of this object will be taken and
serialized on the disk(only for this example, this is seldom the case in real games).

So to get a copy of this Protagonist object the Protagonist object is defined in such a way that
cloning it should be possible. So the following abstract class defines the Prototype object that
we saw in above class diagram.

public abstract class AProtagonist
{
    int m_health;
    int m_felony;
    double m_money;

    public int Health
    {
        get { return m_health; }
        set { m_health = value; }
    }
   
    public int Felony
    {
        get { return m_felony; }
        set { m_felony = value; }
    }

    public double Money
    {
        get { return m_money; }
        set { m_money = value; }
    }

    public abstract AProtagonist Clone();
}

This interface defines all the vital information required for the player and a clone method so that
the object can be cloned. Now lets say we need to spawn a concrete player name CJ in the game. This player
should be Cloneable so that whenever we need to save the game we can simply clone the object and intitiate the
serialization process asynchronously.

class CJ : AProtagonist
{
    public override AProtagonist Clone()
    {
        return this.MemberwiseClone() as AProtagonist;
    }
}

Now this class is the ConcretePrototype that provides the facility to clone itself. In this example the
ConcretePrototype does not contain any specialized members of its own but in some cases this class could contain
some member variable or functions of its own.

Now let us see how the client code will be written to clone this object.

static void Main(string[] args)
{
    // The code to demonstrate the classic Prorotype Pattern
    CJ player = new CJ();
    player.Health = 1;
    player.Felony = 10;
    player.Money = 2.0;

    Console.WriteLine("Original Player stats:");
    Console.WriteLine("Health: {0}, Felony: {1}, Money: {2}", 
        player.Health.ToString(), 
        player.Felony.ToString(), 
        player.Money.ToString());

    // We enter the cheat code here and we have a new 
    // player object that we can save on the disk asyncronously.
    CJ playerToSave = player.Clone() as CJ;            

    Console.WriteLine("\nCopy of player to save on disk:");
    Console.WriteLine("Health: {0}, Felony: {1}, Money: {2}", 
        playerToSave.Health.ToString(), 
        playerToSave.Felony.ToString(), 
        playerToSave.Money.ToString());
}

Understanding Shallow copy and Deep copy

The above code perfectly illustrates the prototype pattern in action. There is one problem though.
We are using the method MemberwiseCopy in our implementation. The problem with the memberwise copy is that
it creates a shallow copy of the object i.e. if the object contains any reference types then only
the address of that reference type will be copied from source to target and both the versions will
keep pointing to the same object.

To illustrate this point lets have a class called AdditionalDetails containing more information about the
Protagonist.

public class AdditionalDetails
{
    int m_charisma;
    int m_fitness;

    public int Charisma
    {
        get { return m_charisma; }
        set { m_charisma = value; }
    }
    
    public int Fitness
    {
        get { return m_fitness; }
        set { m_fitness = value; }
    }
}

Now the extended version of our abstract class will contain a member variable for the

AdditionalDetails

object.

public abstract class AProtagonistEx
{
    int m_health;
    int m_felony;
    double m_money;
    // This is a reference type now
    AdditionalDetails m_details = new AdditionalDetails();

    public int Health
    {
        get { return m_health; }
        set { m_health = value; }
    }

    public int Felony
    {
        get { return m_felony; }
        set { m_felony = value; }
    }

    public double Money
    {
        get { return m_money; }
        set { m_money = value; }
    }

    public AdditionalDetails Details
    {
        get { return m_details; }
        set { m_details = value; }
    }

    public abstract AProtagonistEx Clone();
}

And the Concrete class will still perform a clone operation using Memberwisecopy function.

class CJEx : AProtagonistEx
{
    public override AProtagonistEx Clone()
    {
        return this.MemberwiseClone() as AProtagonistEx;
    }
}

Now the problem with this is that after copy both the versions will be pointing to
the same object of the AdditionalDetials.

static void Main(string[] args)
{
    // The code to demonstrate the shallow copy
    CJEx playerEx = new CJEx();
    playerEx.Health = 1;
    playerEx.Felony = 10;
    playerEx.Money = 2.0;
    playerEx.Details.Fitness = 5;
    playerEx.Details.Charisma = 5;

    // Lets clone the above object and change the 
    // proprties of contained object
    CJEx shallowClonedPlayer = playerEx.Clone() as CJEx;
    shallowClonedPlayer.Details.Charisma = 10;
    shallowClonedPlayer.Details.Fitness = 10;

    // Lets see what happened to the original object
    Console.WriteLine("\nOriginal Object:");
    Console.WriteLine("Charisma: {0}, Fitness: {1}",
        playerEx.Details.Charisma.ToString(),
        playerEx.Details.Fitness.ToString());
    Console.WriteLine("\nShallow Cloned Object:");
    Console.WriteLine("Charisma: {0}, Fitness: {1}",
        shallowClonedPlayer.Details.Charisma.ToString(),
        shallowClonedPlayer.Details.Fitness.ToString());
}

To visualize this problem:

To avoid this what we need to do is to create a copy of the internal reference type on the heap
and then assign that new object with the copy being returned.

class CJEx : AProtagonistEx
{
    public override AProtagonistEx Clone()
    {
        CJEx cloned = this.MemberwiseClone() as CJEx;
        cloned.Details = new AdditionalDetails();
        cloned.Details.Charisma = this.Details.Charisma;
        cloned.Details.Fitness = this.Details.Fitness;

        return cloned as AProtagonistEx;
    }
}

Now when we run the above client code the internal object’s separate copy will be returned.

Note: Care must be taken to perform a deep copy because the reference type could still contain
reference types internally. The ideal way to do a deep copy is to use reflections and keep copying
recursively until the primitive types are reached. For details refer this

So having a shallow copy and deep copy in our object is totally the decision based on the functionality
required but we have seen both the ways of doing the copy.

Implement ICloneable interface for Prototype Pattern

The ICloneable interface in C# serves the purpose of defining the clone method in the objects. We can use
the ICloneable interface as Prototype(from the above class diagram). So let us see the implementation of the
ConcretePrototype by implementing the ICloneable interface.

public class CJFinal : ICloneable
{
    int m_health;
    int m_felony;
    double m_money;
    AdditionalDetails m_details = new AdditionalDetails();

    public int Health
    {
        get { return m_health; }
        set { m_health = value; }
    }

    public int Felony
    {
        get { return m_felony; }
        set { m_felony = value; }
    }

    public double Money
    {
        get { return m_money; }
        set { m_money = value; }
    }

    public AdditionalDetails Details
    {
        get { return m_details; }
        set { m_details = value; }
    }

    private object ShallowCopy()
    {
        return this.MemberwiseClone();
    }

    private object DeepCopy()
    {
        CJFinal cloned = this.MemberwiseClone() as CJFinal;
        cloned.Details = new AdditionalDetails();
        cloned.Details.Charisma = this.Details.Charisma;
        cloned.Details.Fitness = this.Details.Fitness;

        return cloned;
    }

    #region ICloneable Members

    public object Clone()
    {
        return DeepCopy();
    }

    #endregion
}

The client code will now be:

private static void ICloneableVersionCopy()
{
    // Let us see how we can perform the deep copy now
    CJFinal player = new CJFinal();
    player.Health = 1;
    player.Felony = 10;
    player.Money = 2.0;
    player.Details.Fitness = 5;
    player.Details.Charisma = 5;

    // lets clone the object but this time perform a deep copy
    CJFinal clonedPlayer = player.Clone() as CJFinal;
    clonedPlayer.Details.Charisma = 10;
    clonedPlayer.Details.Fitness = 10;

    // Lets see what happened to the original object
    Console.WriteLine("\nOriginal Object:");
    Console.WriteLine("Charisma: {0}, Fitness: {1}",
        player.Details.Charisma.ToString(),
        player.Details.Fitness.ToString());
    Console.WriteLine("\nICloneable Deep Cloned Object:");
    Console.WriteLine("Charisma: {0}, Fitness: {1}",
        clonedPlayer.Details.Charisma.ToString(),
        clonedPlayer.Details.Fitness.ToString());
}

Point of interest

In this article, we saw what is prototype pattern. How can we implement Prototype pattern in C#. We looked
at the Shallow copy and Deep copy concepts in C# and see how can we implement the ICloneable interface in C#.
I hope this has been a little informative.

History 

  • 15 October 2012: First version.

出处:https://www.codeproject.com/Articles/476807/Understanding-and-Implementing-Prototype-Pattern-i

关于作者
ultracpy
评论

    你必须 登录 提交评论