Press "Enter" to skip to content

Prototype Design Pattern in C#

Last updated on July 18, 2021

This post will demonstrate the Prototype design pattern – another pattern for creating instances of objects.

This pattern creates new objects, by “cloning” them from a single (prototype) object.

 

 

Examples

If we were writing a role-playing game, we might want to create multiple instances of monsters for the player to fight.

 

Non-Pattern Version

In the non-prototype version, is we want a new Monster object, we need to call the constructor. If we don’t, and just set a new Monster variable equal to the first Monster object, both variables will be pointing to (referencing) the same object.

 

Monster.cs

namespace Engine.PrototypePattern.NonPatternVersion
{
    public class Monster
    {
        public string Name { get; private set; }
        public int HitPoints { get; private set; }
        public Monster(string name, int hitPoints)
        {
            Name = name;
            HitPoints = hitPoints;
        }
        public void ApplyDamage(int amountOfDamage)
        {
            HitPoints -= amountOfDamage;
        }
    }
}

 

TestMonster.cs

using Engine.PrototypePattern.NonPatternVersion;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.PrototypePattern.NonPatternVersion
{
    [TestClass]
    public class TestMonster
    {
        [TestMethod]
        public void Test_ReferenceProblem()
        {
            Monster spider1 = new Monster("Giant Spider", 10);
            Monster spider2 = spider1;
            Assert.AreEqual(10, spider1.HitPoints);
            Assert.AreEqual(10, spider2.HitPoints);
            spider2.ApplyDamage(2);
            // Even though we only called ApplyDamage on spider2, 
            // the HitPoints for both spider objects is 8.
            //
            // This is because the spider variables are pointing to (referencing)
            // a single spider object - the original spider1.
            Assert.AreEqual(8, spider1.HitPoints);
            Assert.AreEqual(8, spider2.HitPoints);
        }
        [TestMethod]
        public void Test_ReferenceProblemSolution()
        {
            Monster spider1 = new Monster("Giant Spider", 10);
            Monster spider2 = new Monster("Giant Spider", 10);
            Assert.AreEqual(10, spider1.HitPoints);
            Assert.AreEqual(10, spider2.HitPoints);
            spider2.ApplyDamage(2);
            // There is no reference problem, 
            // because we created spider2 by calling the Monster constructor.
            Assert.AreEqual(10, spider1.HitPoints);
            Assert.AreEqual(8, spider2.HitPoints);
        }
    }
}

 

In the first test (Test_ReferenceProblem), we create “standardGiantSpider” – the “base” giant spider object.

When we create a new spider variable, and set it to standardGiantSpider, it will not be a separate object. Instead, it will “reference” the original standardGiantSpider object.

Anything change done to one spider object, appears on all spider objects, because they are all pointing to a single object.

To prevent this problem, without using the Prototype design pattern, we would need to call the constructor for every Monster object we create – as in the second test (Test_ReferenceProblemSolution).

 

 

Pattern Version – Simple

 

Monster.cs

namespace Engine.PrototypePattern.PatternVersion_Simple
{
    public class Monster
    {
        public string Name { get; private set; }
        public int HitPoints { get; private set; }
        public Monster(string name, int hitPoints)
        {
            Name = name;
            HitPoints = hitPoints;
        }
        public void ApplyDamage(int amountOfDamage)
        {
            HitPoints -= amountOfDamage;
        }
        public Monster Clone()
        {
            return new Monster(Name, HitPoints);
        }
    }
}

The constructor is called the first time, to create a prototype giant spider object. To create more giant spider objects, we will call a “Clone()” method on the prototype giant spider object. This will create completely new objects, because the Clone method calls the Monster constructor.

 

TestMonster.cs

using Engine.PrototypePattern.PatternVersion_Simple;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.PrototypePattern.PatternVersion_Simple
{
    [TestClass]
    public class TestMonster
    {
        [TestMethod]
        public void Test_PrototypePattern()
        {
            Monster standardGiantSpider = new Monster("Giant Spider", 10);
            // Calling the Clone method is how the Prototype Pattern is implemented.
            Monster spider2 = standardGiantSpider.Clone();
            Assert.AreEqual(10, standardGiantSpider.HitPoints);
            Assert.AreEqual(10, spider2.HitPoints);
            spider2.ApplyDamage(2);
            // There is no reference problem,
            // because the Clone method constructs a new Monster object.
            Assert.AreEqual(10, standardGiantSpider.HitPoints);
            Assert.AreEqual(8, spider2.HitPoints);
        }
    }
}

In the second test (Test_PrototypePattern), we call the Clone method on the initial standardGiantSpider object. Because the Clone method return a “new Monster”, the additional spider variables are referencing individual, unique objects. Anything done to one spider variable will not affect the other spider variables.

 

Pattern Version – More Complex

Here is a more practical scenario for using the Prototype design pattern.

In this version of the Monster class, the constructor loads a LootTable list. These are items the Monster could have in its inventory, with the percentage of it having that item.

In a real program, we would probably load this from the database – although, for this example, I manually populate the LootTable with LootTableEntry values.

If we called the constructor every time we created a Monster object, it would need to re-run the database call. So, in this example, the Clone method calls a private constructor that populates the LootTable property with the values from the prototype’s LootTable property.

 

Monster.cs

using System.Collections.Generic;
namespace Engine.PrototypePattern.PatternVersion_Complex
{
    public class Monster
    {
        public string Name { get; private set; }
        public int HitPoints { get; private set; }
        public List<LootTableEntry> LootTable { get; set;}
        // Public constructor, called to create the prototype Monster object.
        public Monster(string name, int hitPoints)
        {
            Name = name;
            HitPoints = hitPoints;
            // In this part, pretend we are populating LootTable using a database query.
            LootTable = new List<LootTableEntry>();
            LootTable.Add(new LootTableEntry { ItemID = 1, DropPercentage = 10 });
            LootTable.Add(new LootTableEntry { ItemID = 2, DropPercentage = 5 });
            LootTable.Add(new LootTableEntry { ItemID = 5, DropPercentage = 1 });
            LootTable.Add(new LootTableEntry { ItemID = 12, DropPercentage = 50 });
            LootTable.Add(new LootTableEntry { ItemID = 27, DropPercentage = 33 });
            LootTable.Add(new LootTableEntry { ItemID = 42, DropPercentage = 100 });
        }
        // Private constructor called by Clone method.
        // Does not need to connect to the database to populate the LootTable property.
        private Monster(string name, int hitPoints, List<LootTableEntry> lootTable)
        {
            Name = name;
            HitPoints = hitPoints;
            LootTable = lootTable;
        }
        public void ApplyDamage(int amountOfDamage)
        {
            HitPoints -= amountOfDamage;
        }
        public Monster Clone()
        {
            // This version of Clone calls the private constructor,
            // to prevent re-running the database query to populate LootTable.
            return new Monster(Name, HitPoints, LootTable);
        }
    }
}

 

LootTableEntry.cs

namespace Engine.PrototypePattern.PatternVersion_Complex
{
    public class LootTableEntry
    {
        public int ItemID { get; set; }
        public int DropPercentage { get; set; }
    }
}

 

In both these scenarios, we could have created the Monster objects using the Factory design pattern. The Prototype design pattern is just another “tool in your toolbox” – to use when it seems appropriate.

 

 

Where I’ve found it useful

I usually do not need to use this in my business applications. However, I have used it in a game – just like in the “Better Example” version.

This pattern is most useful when you’ll need to create multiple instances of an objects, and the constructor has a lot of initialization to perform. With a prototype, you only need to do that initialization once.

 

All my design pattern lessons

Source code for my design pattern lessons

 

    Leave a Reply

    Your email address will not be published. Required fields are marked *