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:

Prototype Design

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.

💡
For easy prototype implementation, you may add a factory method that stores frequently used prototypes in a pool and provides various configured prototypes based on the client's request.

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
Prototype Pattern Code Example

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.