Design Patterns Software Engineering

Design Patterns – Prototype

Pinterest LinkedIn Tumblr

The code is available on GitHub

The Prototype Design Pattern allows us to create objects by cloning a prototypical instance instead of creating them from scratch or using a factory class. You can use many prototypical instances that can produce different copies from the original objects. The copies can vary from their prototype satisfying this way different needs. The Prototype Design Pattern is also considered as alternative to subclassing for object creation.

When to use

Use the Prototype Pattern if you want to create an object with the same property values from another existing object.

The Prototype Pattern is ideal when we need to create instances with identical property values with an existing object. Rather than constructing objects from scratch, we can clone a prototype instance and modify specific properties as needed. This approach saves time and avoids the repetition of possible costly constructor calls.

Let’s consider an example where we have a Monster class and we use a prototype instance of that class in order to create and spawn many monsters that are very close related to the prototypical instance. Instead of creating the object from scratch, we call the Clone method of the prototype as shown below.

public class Graphics
{
   public void Draw(Monster monster, Point position)
   { /* draw monster at position */ }
}

public class Monster : ICloneable
{
   public string Name { get; set; }
   public string Kind { get; set; }

   public Graphics Graphics { get; }

   public Monster()
   {
      Graphics = new Graphics(); // Time consuming functionality. 
   }

   private Monster(Graphics graphics, string name, string kind)
   {
      Graphics = graphics;
      Name = name;
      Kind = kind;
   }

   public void Draw()
      => Graphics.Draw(this, position: new Point(10, 10));

   // Using the Clone method we return a new instance.
   public object Clone()
   {
      // We pass the properties of this instance.
      return new Monster(Graphics, Name, Kind);
   }
}

// Usage.
Monster prototype = new Monster() { 
   Name = "Alchemist",
   Kind = "Agility"
};

// We want the same Graphics instance in all of our objects so the prototype instance
// acts as the prototype we clone.
var newMonster = prototype.Clone() as Monster;
newMonster.Name = "Doom"; // Afterwards we can alter any property we want.
newMonster.Draw();

Use the Prototype Pattern to avoid having a hierarchy of factory classes that mirrors the hierarchy of objects you want to copy or create.

When dealing with complex hierarchies of objects, the Prototype Pattern can help us avoid creating a corresponding hierarchy of factory classes. Instead, we can add a clone functionality to each object in the hierarchy, enabling recursive cloning of the entire object tree.

Imagine a scenario where we have a hierarchy of Ingredient objects within a Recipe class. We can clone the whole hierarchy of ingredients a Recipe has, by recursively calling the Clone method of each object in the hierarchy tree.

Use the Prototype Pattern to create instances from an object that may be very similar to the original.

The Prototype Pattern is useful when we want to create instances that share similarities with an existing object. By using a prototypical instance as a template, we can clone it and modify the copies accordingly.

Below, we use a prototypical instance that has different newsletter instances for different purposes, each one containing a different HTML template. Those instances can be used in the application as prototypes. Then, we can clone and modify the appropriate prototype depending on the scenario.

Use the Prototype Pattern when an instance has only a few different combinations of state.

When objects have a finite number of distinct states, it can be more efficient to create a set of predefined prototypes and clone them as needed. This approach eliminates the need to manually create instances and set specific states/properties each time.

Below we have 4 different configurations an application can get depending on the user role (Admin, User, Editor, Moderator). Those configurations exist as different prototypical instances and can be cloned and used accordingly.

Use the Prototype Pattern when you want to create an instance of a class that is known at runtime.

The Prototype Pattern can be used when we want to create instances of a class dynamically, without prior knowledge about the class. By encapsulating the cloning within the class itself and by using a common interface, we can load classes at runtime and clone them.

* The problem of dynamic loading can also be solved more appropriately by using Dependency Injection.

How to implement

Define an IPrototype interface with a Clone() method that returns an object.

Begin by defining an interface called IPrototype which includes a Clone() method that returns an object. This interface provides a uniform way of handling the creation of different prototypes.

public interface IPrototype
{
   object Clone();
}

Decide which classes should implement the IPrototype interface.

Next, for each of these classes, implement the Clone() method to create a new object of the same class and copy the values of all the attributes from the original object to the new object.

You can choose to perform a shallow or deep copy, depending on your requirements.

Below we present an example of two classes implementing the IPrototype interface.

public class ConcretePrototype1 : IPrototype
{
   public string Property1 { get; set; }
   public string Property2 { get; set; }

   public ConcretePrototype1() { }

   private ConcretePrototype1(ConcretePrototype1 source)
   {
      Property1 = source.Property1;
      Property2 = source.Property2;
   }

   public object Clone() => new ConcretePrototype1(this);
}

public class ConcretePrototype2 : IPrototype
{
   public string Property3 { get; set; }
   public string Property4 { get; set; }

   public ConcretePrototype2() { } 
 
   private ConcretePrototype2(ConcretePrototype2 source)
   {
      Property3 = source.Property3;
      Property4 = source.Property4;
   }

   public object Clone() => new ConcretePrototype2(this);
}

Optional – Create a prototype manager or registry to handle the various prototypes.

This manager class can have methods to register new prototypes and also retrieve them.

There are different approaches you can take for the registry, such as passing all instances in the constructor or dynamically registering prototypes using a Register method and providing a key for retrieval.

In the example below, we show a registry that contains two types of Monsters that we can access them through public properties of the registry.

public class PrototypeRegistry
{
   public IntelligenceMonster IntelligenceMonsterPrototype;
   public StrengthMonster StrengthMonsterPrototype;

   public PrototypeRegistry(IntelligenceMonster intelligenceMonsterPrototype,
      StrengthMonster strengthMonsterPrototype)
   {
      IntelligenceMonsterPrototype = intelligenceMonsterPrototype;
      StrengthMonsterPrototype = strengthMonsterPrototype;
   }
}

var strengthMonsterPrototype = new StrengthMonster(new {power = "empty" });
var intelligenceMonsterPrototype = new IntelligenceMonster(100);

var registry = new PrototypeRegistry(intelligenceMonsterPrototype, strengthMonsterPrototype);

var grimstroke = registry.IntelligenceMonsterPrototype.Clone() as IntelligenceMonster;
grimstroke.ManaPoints = 200;
grimstroke.Draw();

The other approach is to have a Register method in our registry that we call to register every prototype we need. In order to access the instances we provide a key as along with the prototype instance in order to be able to obtain the instance we wanted with a Get method. The following example demonstrates this approach.

public class DynamicPrototypeRegistry
{
   private readonly Dictionary<string, Monster> _prototypes = new();

   public void Register(string key, Monster prototype)
      => _prototypes.Add(key, prototype);

   public Monster Get(string key)
      => _prototypes[key];
}

var strengthMonsterPrototype = new StrengthMonster(new { power = "empty" });
var intelligenceMonsterPrototype = new IntelligenceMonster(100);

var registry = new DynamicPrototypeRegistry();
registry.Register("strength", strengthMonsterPrototype);
registry.Register("intelligence", intelligenceMonsterPrototype);

var grimstroke = registry.Get("intelligence").Clone() as IntelligenceMonster;
grimstroke.ManaPoints = 200;
grimstroke.Draw();

Shallow Copy vs Deep Copy

When implementing the Prototype Pattern, you need to consider whether to use a shallow copy or a deep copy.

  • Shallow Copy – A shallow copy creates a new object and copies the values of all the fields from the original object to the new object. However, if the fields of the original object contain references to other objects, the shallow copy simply copies the references, not the actual objects. As a result, both the original object and the copied object will refer to the same objects. In other words, a shallow copy shares the internal references with the original object.
  • Deep Copy – A deep copy, on the other hand, creates a new object and recursively copies the values of all the fields from the original object to the new object. If the fields of the original object contain references to other objects, the deep copy also creates new copies of those referenced objects and assigns them to the corresponding fields in the copied object. This ensures that the copied object has its own independent copies of all the objects it references. This can be handy in cases where we want to alter the copied object without affecting other references. This approach is usually more expensive than a Shallow copy.

Prototype Pattern – Class diagram

In the class diagram below, we can observe the different components of the Prototype Pattern. The client interacts with a prototype object by requesting a clone. Instead of directly instantiating new objects, the client can create instances of concrete prototypes by cloning them.

Real World Example

Many programming languages, such as Java and C#, offer built-in support for cloning objects, making it easier to implement the Prototype Pattern. In Java, the Cloneable interface is used, while in C#, the ICloneable interface is utilized.

Additionally, those languages provide an out of the box mechanism for copying objects properties without requiring additional manual work.

ICloneable in .NET custom objects

In the following example, we demonstrate in C# how the ICloneable interface can be utilized to clone an object.

public class Customer : ICloneable
{
   public string FirstName { get; }
   public string LastName { get; }
   public List<Order> Orders { get; }

   public Customer(string firstName, string lastName, List<Order> orders)
   {
      FirstName = firstName;
      LastName = lastName;
      Orders = orders;
   }

   public object Clone() => MemberwiseClone();
}

public class Order
{
   public DateTime Created { get; }
   public string ItemCode { get; }
   public int Count { get; set; }

   public Order(DateTime created, string itemCode, int count)
   {
      Created = created;
      ItemCode = itemCode;
      Count = count;
   }
}

The MemberwiseClone copies the properties of the object one by one. We can then clone a customer object like below:

var orders = new List<Order>()
{
   new Order(new DateTime(2023, 2,2), "Item1", 4),
   new Order(new DateTime(2023, 2,5), "Item2", 2)
};
Customer prototype = new Customer("Iron", "Man", orders);

var clonedCustomer = prototype.Clone() as Customer;
clonedCustomer.Orders[0].Count = 0; // Changing this will result also an update in the prototype!

Using MemberwiseClone makes easy to get a clone of an object but this will result in a shallow copy! To achieve a deep copy, the ICloneable interface needs to be implemented differently in the Customer class in order to copy each Order object.

An updated implementation for a deep copy for the Customer objects is demonstrated below.

public class Customer : ICloneable
{
   public string FirstName { get; }
   public string LastName { get; }
   public List<Order> Orders { get; }

   public Customer(string firstName, string lastName, List<Order> orders)
   {
      FirstName = firstName;
      LastName = lastName;
      Orders = orders;
   }

   public object Clone()
   {
      var orders = Orders.Select(x => x.Clone() as Order).ToList();
      var clone = new Customer(FirstName, LastName, orders);
      return clone;
   }
}

public class Order : ICloneable
{
   public DateTime Created { get; }
   public string ItemCode { get; }
   public int Count { get; set; }

   public Order(DateTime created, string itemCode, int count)
   {
      Created = created;
      ItemCode = itemCode;
      Count = count;
   }

   public object Clone() => MemberwiseClone();
}

ICloneable for .NET objects

The .NET framework already includes various objects that implement the ICloneable interface, such as Array, ArrayList, and String. Below we show how we can use the prototype Array, clone it, and modify one of its elements.

Array arr = new int[] { 1, 2, 3, 4, 5 };

var cloned = (Array)arr.Clone();
cloned.SetValue(100, 0);

foreach (var item in arr)
   Console.Write(item + " ");

Console.WriteLine();

foreach (var item in cloned)
   Console.Write(item + " ");

The output of the previous code will display the elements of both the original and cloned arrays.

1 2 3 4 5 // Original.
100 2 3 4 5 // Cloned.

Example – Expression Tree

In this section, we demonstrate how we can clone a tree of expressions in order to manipulate it without affecting any object from the tree that the clone came from.

First, we define an abstract class called Expression that implements the ICloneable interface. This class serves as the base for different types of expressions.

public abstract class Expression : ICloneable
{
   public abstract object Clone();
}

Next, we introduce four specific types of expressions that extend the Expression abstract class.

  • Literal – This expression encapsulates a single value, such as numbers, strings and Booleans.
  • Unary – It performs logical operations like logical negation or negating a number.
  • Binary – It carries out operations between two expressions, returning a result. It supports various arithmetic and comparison operators, such as addition, subtraction, multiplication, division, and comparison operators like greater and less than.
  • Logical – It performs logical operations, such as logical AND or OR, between two expressions, yielding a Boolean result.

Each expression type provides its own implementation of the Clone method. Additionally, each expression type is capable of returning a string representation of itself, making it easier to display the entire expression.

public class Binary : Expression
{
   public Expression Left { get; }
   public Expression Right { get; }

   public Operator Operator { get; }

   public Binary(Expression left, Operator @operator, Expression right)
   {
      Left = left;
      Operator = @operator;
      Right = right;
   }

   public override object Clone()
      => new Binary(Left.Clone() as Expression, Operator, Right.Clone() as Expression);

   public override string ToString()
      => $"{Left} {Operator.AsString()} {Right}";
}

public class Literal : Expression
{
   public object Value { get; set; }

   public Literal(object value)
   {
      Value = value;
   }

   public override object Clone()
      => MemberwiseClone();

   public override string ToString() => Value.ToString();
}

public class Logical : Expression
{
   public Expression Left { get; }
   public Operator Operator { get; }
   public Expression Right { get; }

   public Logical(Expression left, Operator @operator, Expression right)
   {
      Left = left;
      Operator = @operator;
      Right = right;
   }

   public override object Clone()
      => new Binary(Left.Clone() as Expression, Operator, Right.Clone() as Expression);

   public override string ToString()
      => $"{Left} {Operator.AsString()} {Right}";
}

public class Unary : Expression
{
   public Expression Expression { get; }
   public Operator Operator { get; }

   public Unary(Expression expression, Operator @operator)
   {
      Expression = expression;
      Operator = @operator;
   }

   public override object Clone()
      => new Unary(Expression.Clone() as Expression, Operator);

   public override string ToString()
      => $"{Operator.AsString()}{Expression}";
}

Each expression type implements the Clone method by creating a deep copy of itself, cloning its subexpressions recursively. The ToString method is overridden to provide a string representation of the expression.

The Operator enum represents different types of operators used in the expressions.

public enum Operator
{
   OR, // ||
   AND, // &&
   BANG, // !
   BANG_EQUAL, // != 
   EQUAL_EQUAL, // ==
   GREATER, // >
   GREATER_EQUAL, // >=
   LESS, // <
   LESS_EQUAL, // <=
   MINUS, // -
   PLUS, // +
   SLASH, // /
   STAR // *
}

In the following example, we create a simple expression by combining different expressions using operators.

var ten = new Literal(10);
var prototype = new Binary(new Unary(ten, Operator.MINUS), Operator.GREATER,
   new Unary(new Literal(100), Operator.MINUS));
Console.WriteLine(prototype.ToString()); // Output: -10 > -100

Afterwards, we clone the prototype and modify the ten expression. We observe that the cloned expression remains unaffected by the changes, because of the deep cloning implementation inside the Clone methods. Similarly, we make a change in the cloned expression and check that the prototype remains unaffected by the change.

var cloned = prototype.Clone();

ten.Value = 1000; // Even we change this value the cloned object is not affected,
                  // because of the deep copy.

Console.WriteLine(prototype.ToString()); // Output: -1000 > -100
Console.WriteLine(cloned.ToString()); // Output: -10 > -100 - not affected

(((cloned as Binary).Left as Unary).Expression as Literal).Value = 50;
Console.WriteLine(prototype.ToString()); // Output: -1000 > -100 - not affected
Console.WriteLine(cloned.ToString()); // Output: -50 > -100

Cloneable Expression Tree – Class Diagram

Below we present the class diagram of the expression tree example. As we can see we every Expression has its own implementation of the Clone method.

Using Dependency Injection

Dependency injection is a powerful technique that enables flexible and loosely coupled code. In this section, we’ll explore how to use dependency injection for implementing the Prototype Pattern.

Autofac

The code with dependency injection is available on GitHub

C#

We can use Dependency Injection as a IPrototype / ICloneable registry. We can register all prototypes along with a key and retrieve them later in order to clone them. As a lifetime scope we use SingleInstance because we want the prototypes to live once in our application’s lifetime scope.

var builder = new ContainerBuilder();

// Register the prototypes.
builder.RegisterInstance(new StrengthMonster(new { power = "empty" }))
   .Keyed<ICloneable>("strength")
   .SingleInstance();
builder.RegisterInstance(new IntelligenceMonster(100))
   .Keyed<ICloneable>("intelligence")
   .SingleInstance();

var container = builder.Build();

// We can later retrieve them.
var grimstroke = container.ResolveKeyed<ICloneable>("intelligence").Clone() as IntelligenceMonster;
grimstroke.ManaPoints = 200;
grimstroke.Draw();

Similar Patterns

With Abstract Factory we can achieve similar results with the Prototype

The Composite Pattern can benefit from Prototype by allowing the cloning of the entire hierarchy tree.

Further Reading

Looking to dive deeper into design patterns and take your programming skills to the next level? Check out the following highly recommended books:

clean code cover

Clean Code

Author: Robert C. Martin
Publication: August 1, 2008

head first design patterns

Head First – Design Patterns

Authors: Eric Freeman, Elisabeth Robson
Publication: January 12, 2021

* This site contains product affiliate links. We may receive a commission if you make a purchase after clicking on one of these links.

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.