F0llowing from my previous blogs on Why We Should Use Design Patterns and Simple Factory, Factory Method and Abstract Factory Patterns, here we go with another creational pattern. The factory patterns I discussed in my previous article were also creational, if you recall correctly. This time we will be discussing The Prototype Pattern.
The Prototype Pattern
You might think, "Why another creational pattern if we already have the factory patterns?" Well, the answer is simple: different patterns have different use cases. While factory patterns are used to create products at runtime, the prototype pattern is mainly focused on creating objects specified in runtime by avoiding the creation of subclasses in the client program itself.
Use Case
The Prototype Pattern becomes really useful if you want to exact the same clone of an object without knowing its real implemented class. If we create an object without this pattern using a new keyword, we won't be able to copy the private fields of that object.
Not using this pattern will also make your code messy because we will depend on the concrete implementation of the original class when we have to know what the current cloned object is.
Design Diagram
The design diagram of the prototype pattern is as follows:

How to Implement a Prototype Design Pattern?
Let's go through implementing such a design pattern with an example.
First, create an interface that has methods for cloning objects.
In my example below I have made 2 methods; one that provides a shallow clone – for which the reference type properties inside the cloned object will point to the same memory address as the original object – and another that provides a deep clone – for which the cloned object and original object's properties will point to completely different memory addresses.
We can now create a class that implements a prototype interface. This concrete class will then implement the cloning function and return a copy of itself. In my example, I have created a class Player
that implements IPlayer
. Player
then returns a cloned version of itself when using the GetShallowClone()
and GetDeepClone()
methods. I have created p1
as an original object and cloned it and assigned it to p2
and p3
in my example below.
Code and Execution
namespace DesignPattern.Patterns.Prototype_Pattern
{
public interface IPlayer
{
IPlayer GetShallowClone();
IPlayer GetDeepClone();
}
}
using System;
namespace DesignPattern.Patterns.Prototype_Pattern
{
public class Player :IPlayer
{
public string name { get; set; }
public Stats stats;
public Player(string name, Stats stats)
{
this.name = name;
this.stats = stats;
}
public IPlayer GetShallowClone()
{
return MemberwiseClone() as IPlayer;
}
public IPlayer GetDeepClone()
{
Player player = MemberwiseClone() as Player;
player.stats = new Stats{level = 10,health = 150};
return player;
}
public void ShowStats()
{
Console.WriteLine($"Name is : {name}");
Console.WriteLine($"Health : {stats.health}");
Console.WriteLine($"Current Level : {stats.level}");
}
}
}
namespace DesignPattern.Patterns.Prototype_Pattern
{
public class Stats
{
public int health { get; set; }
public int level { get; set; }
}
}
using System;
namespace DesignPattern.Patterns.Prototype_Pattern
{
public class PrototypeDemo
{
public void Run()
{
Console.WriteLine("--------------- p1 stats ----------------");
Player p1 = new Player("Player 1", new Stats {level = 20, health = 440});
p1.ShowStats();
Console.WriteLine("\n--------------- p2 stats ----------------");
Player p2 = p1.GetShallowClone() as Player;
p2.ShowStats(); // this object has all the information of p1
Console.WriteLine("\n--------------- New p2 stats ----------------");
p2.stats.level = 3;
p2.name = "Player 2 ";
p2.ShowStats(); // stats value is changed in p2 object
Console.WriteLine("\n--------------- p1 stats ----------------");
p1.ShowStats(); // changing p2 has also affected p1 because it was a shallow copy.
Console.WriteLine("\n--------------- p3 stats ----------------");
Player p3 = p1.GetDeepClone() as Player; // we create a deep copy
p3.ShowStats(); // p3 has same values as p1
Console.WriteLine("\n--------------- New p3 stats ----------------");
p3.stats.level = 25; // we change value of p3 to check if it will affect p1 again
p3.name = "Player 3 ";
p3.ShowStats(); // show updated value of p3
Console.WriteLine("\n--------------- p1 stats ----------------");
p1.ShowStats(); // p1 has not changed, meaning deep copy was a success
}
}
}
using DesignPattern.Patterns.Prototype_Pattern;
namespace DesignPattern
{
internal class Program
{
public static void Main(string[] args)
{
PrototypeDemo prototypeDemo = new PrototypeDemo();
prototypeDemo.Run();
}
}
}
This program produces following output :
--------------- p1 stats ----------------
Name is : Player 1
Health : 440
Current Level : 20
--------------- p2 stats ----------------
Name is : Player 1
Health : 440
Current Level : 20
--------------- New p2 stats ----------------
Name is : Player 2
Health : 440
Current Level : 3
--------------- p1 stats ----------------
Name is : Player 1
Health : 440
Current Level : 3
--------------- p3 stats ----------------
Name is : Player 1
Health : 150
Current Level : 10
--------------- New p3 stats ----------------
Name is : Player 3
Health : 150
Current Level : 25
--------------- p1 stats ----------------
Name is : Player 1
Health : 440
Current Level : 3
The prototype pattern is really helpful when we want to create a cloned object of an already present object. It helps in reducing repeated initialization of complex objects using a centralized clone system that clones all required properties.
That's all for today , I will be coming with more design patterns soon.