Prototype Design Pattern in C#
Golam Mostofa
??Unity 3D Developer |??Crafting Engaging Games |??Turning Ideas into Reality
This article explains how to implement prototype design pattern in C# and where it can be used.
What is this
Prototype design pattern comes under creational design pattern category of Gang of four (GoF) design patterns.GoF defines prototype pattern as "Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype."
Why Prototype Pattern
The prototype pattern is used when creating an instance of a class is very time consuming or complex in some way or it is overhead to create new object which may be cause of performance. Then rather than creating more instances, it is possible to make copies of the original instances and modifying them. This helps to copy or clone the existing objects to create new ones rather than creating from the scratch. When we are not in a situation in which we can call an object's constructor directly, we could clone a pre-existing instance of the object (our prototype). Rather than creating more and more instances of said object it's possible to save overhead by cloning an existing copy of your object.
When working with this design method we must create a class using the abstract modifier, meaning that it will be the only class your object will implement/inherit from. One thing to consider when using this design pattern is determining whether we want a deep clone or shallow clone.
What is Shallow & Deep Clone
Shallow clone: can be performed with using the Object.MemberWiseClone() Method, which copies the non-static fields of the original object. With reference types the reference is copied meaning the original and cloned object reference to the same instance, so if a value is changed in the original object the shallow cloned object with be updated.
Deep Clone: A deep clone copies both reference and value types, giving 2 distinct instances of an object, so if object A is modified the cloned object (object B) remains unchanged. While this may be preferred in some cases, remember that there is more overhead when performing a deep clone.
Prototype Pattern in a diagram
- IPrototype -- this defines an interface for cloning itself
- ConcretePrototype -- this defines a type that implements the operation for cloning itself
- Client -- this defines the user that can create a new instance by cloning a prototype
Lets Get Dive on the Code
Let's say we have a enemy class, The enemy has attribute like health, damage, attack power. First, we'll create an abstract class (the Prototype participant) to represent a Enemy, and define a method by which the abstract Enemy class can clone itself:
Enemy.cs
public abstract class Enemy
{
int m_Health;
int m_Attck;
double m_Damage;
public int Health
{
get { return m_Health; }
set { m_Health = value; }
}
public int Attack
{
get { return m_Attck; }
set { m_Attck = value; }
}
public double Damage
{
get { return m_Damage; }
set { m_Damage = value; }
}
public abstract Enemy Clone();
}
This interface defines all the vital information required for the Enemy and a clone method so that the object can be cloned. Now let's say we need to spawn a concrete Enemy name Elephant in the game. This Enemy should be Cloneable so that whenever we need to use any duplication of our object, we can simply clone the object and initiate the serialization process asynchronously.
public class Elephant : Enemy
{
public override Enemy Clone()
{
return this.MemberwiseClone() as Enemy;
}
}
Now this class is the ConcretePrototype that provides the facility to clone itself.
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
Elephant elephant = new Elephant();
elephant.Health = 100;
elephant.Attack = 10;
elephant.Damage = 5.0;
Console.WriteLine("Original Enemy stats:");
Console.WriteLine("Health: {0}, Attack: {1}, Damage: {2}",
elephant.Health.ToString(),
elephant.Attack.ToString(),
elephant.Damage.ToString());
Elephant playerToSave = elephant.Clone() as Elephant;
Console.WriteLine("\nCopy of Enemy to save on disk:");
Console.WriteLine("Health: {0}, Attack: {1}, Damage: {2}",
playerToSave.Health.ToString(),
playerToSave.Attack.ToString(),
playerToSave.Damage.ToString());
}
The above code describes the prototype pattern. There is one problem here. We are using the method MemberwiseCopy in this implementation. The problem with the memberwise copy is that it creates a shallow copy of the object, that means if the object contains any reference types then only the address of that reference will be copied from source to target and both the versions will keep pointing to the same object.
Lets see this problem with another example. We add a additional behaviour in our enemy class Trait-
EnemyTrait.cs
public class EnemeyTrait
{
int boostTime;
int boostDamage;
public int BoostTime
{
get
{
return boostTime;
}
set
{
boostTime = value;
}
}
public int BoostDamage
{
get
{
return boostDamage;
}
set
{
boostDamage = value;
}
}
}
Now let's add this Trait to our abstract class as a Member Variable.
Enemy.cs
public abstract class Enemy
{
int m_Health;
int m_Attck;
double m_Damage;
//Added here
EnemeyTrait m_EnemyTrait = new EnemeyTrait();
public int Health
{
get { return m_Health; }
set { m_Health = value; }
}
public int Attack
{
get { return m_Attck; }
set { m_Attck = value; }
}
public double Damage
{
get { return m_Damage; }
set { m_Damage = value; }
}
public EnemeyTrait EnemeyTrait
{
get
{
return m_EnemyTrait;
}
set
{
m_EnemyTrait = value;
}
}
public abstract Enemy Clone();
}
public class Elephant : Enemy
{
public override Enemy Clone()
{
return this.MemberwiseClone() as Enemy;
}
}
Okay, let’s go to main function and see what happened.
static void Main(string[] args)
{
// The code to demonstrate the classic Prorotype Pattern
Elephant elephant = new Elephant();
elephant.Health = 100;
elephant.Attack = 10;
elephant.Damage = 5.0;
elephant.EnemeyTrait.BoostTime = 7;
elephant.EnemeyTrait.BoostDamage = 7;
Elephant playerToSave = elephant.Clone() as Elephant;
playerToSave.EnemeyTrait.BoostTime = 10;
playerToSave.EnemeyTrait.BoostDamage = 10;
Console.WriteLine("Original Enemy stats:");
Console.WriteLine("BoostTime: {0}, BoostDamage: {1}",
elephant.EnemeyTrait.BoostTime.ToString(),
elephant.EnemeyTrait.BoostDamage.ToString());
Console.WriteLine("Copied Object Value:");
Console.WriteLine("BoostTime: {0}, BoostDamage: {1}",
playerToSave.EnemeyTrait.BoostTime.ToString(),
playerToSave.EnemeyTrait.BoostDamage.ToString());
}
Let’s visualize this problem:
What should we do to avoid 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.
public class Elephant : Enemy
{
public override Enemy Clone()
{
Elephant elephant = this.MemberwiseClone() as Elephant;
elephant.EnemeyTrait = new EnemeyTrait();
elephant.EnemeyTrait.BoostDamage = this.EnemeyTrait.BoostDamage;
elephant.EnemeyTrait.BoostTime = this.EnemeyTrait.BoostTime;
return elephant as Enemy;
}
}
Now if we run the main program we see the following output
Care must be taken to perform a deep copy because the reference type could still contain reference types internally. The good way to do a deep copy is to use reflections and keep copying recursively until the primitive types are reached.
So having a shallow copy and deep copy in our object is totally the decision based on the functionality required.
Implement ICloneable interface for Prototype Pattern
We can implement C# ICloneable interface for this prototype pattern. Lets see how can it be done. Let’s see the changes
Enemy.cs
using System;
public class Enemy : ICloneable
{
int m_Health;
int m_Attck;
double m_Damage;
//Added here
EnemeyTrait m_EnemyTrait = new EnemeyTrait();
public int Health
{
get { return m_Health; }
set { m_Health = value; }
}
public int Attack
{
get { return m_Attck; }
set { m_Attck = value; }
}
public double Damage
{
get { return m_Damage; }
set { m_Damage = value; }
}
public EnemeyTrait EnemeyTrait
{
get
{
return m_EnemyTrait;
}
set
{
m_EnemyTrait = value;
}
}
private object ShallowCopy()
{
return this.MemberwiseClone();
}
private object DeepCopy()
{
Enemy cloned = this.MemberwiseClone() as Enemy;
cloned.EnemeyTrait = new EnemeyTrait();
cloned.EnemeyTrait.BoostDamage = this.EnemeyTrait.BoostDamage;
cloned.EnemeyTrait.BoostTime = this.EnemeyTrait.BoostTime;
return cloned;
}
public object Clone()
{
//return what copy you want to do
// return ShallowCopy();
// return DeepCopy();
}
}
Deep copy using ICloneable
Shallow copy Using ICloneable
Happy Coding!!