What is the Prototype Design Pattern?

Göksu Deniz
9 min readJan 12, 2023

--

Image is created by DALL-E 2 AI

The prototype design pattern is a creational design pattern that allows an object to be created by copying an existing object, rather than creating a new instance from scratch. The existing object, known as the prototype, serves as a template for creating new objects. This can be useful in situations where creating a new instance from scratch is expensive or time-consuming, or where the desired object is not easily instantiated using standard constructors. The prototype pattern is often used in combination with other design patterns, such as the factory pattern, to provide a more flexible and efficient way of creating objects.

To implement the prototype pattern, an interface or abstract class is defined that specifies a method for creating a copy of the object. This method is typically called “clone” or “copy”. Concrete classes that implement this interface or inherit from this abstract class are responsible for providing an implementation of the clone or copy method.

When a new object needs to be created, the client code can call the clone or copy method on an existing prototype object to create a new instance, rather than creating a new instance using the class’s constructor. This can be useful in situations where creating a new instance from scratch is expensive or time-consuming, or where the desired object is not easily instantiated using standard constructors.

The prototype pattern has several advantages:

  • It allows objects to be created without specifying their class, which can make the code more flexible and reusable.
  • It can improve performance by avoiding the costly process of creating a new object from scratch.
  • It can be used to create multiple instances of the same object with slightly different configurations.

The prototype pattern is often used in combination with other design patterns, such as the factory pattern, to provide a more flexible and efficient way of creating objects. The factory pattern can be used to create a prototype object and return it to the client code, which can then use the clone or copy method to create new instances.

Overall, the prototype pattern is useful when creating an object is expensive and you need a similar object. It provides a way to create a new object by copying an existing object, rather than creating a new instance from scratch.

Here’s a simple example of the prototype pattern in C#:

abstract class Prototype
{
public abstract Prototype Clone();
}

class ConcretePrototypeA : Prototype
{
public int X { get; set; }
public int Y { get; set; }

public override Prototype Clone()
{
// Create a new instance of ConcretePrototypeA and copy the values of X and Y
ConcretePrototypeA clone = new ConcretePrototypeA();
clone.X = this.X;
clone.Y = this.Y;
return clone;
}
}

class ConcretePrototypeB : Prototype
{
public string Name { get; set; }
public List<int> Numbers { get; set; }

public override Prototype Clone()
{
// Create a new instance of ConcretePrototypeB and copy the values of Name and Numbers
ConcretePrototypeB clone = new ConcretePrototypeB();
clone.Name = this.Name;
clone.Numbers = new List<int>(this.Numbers);
return clone;
}
}

class Client
{
public void Main()
{
// Create a new instance of ConcretePrototypeA
ConcretePrototypeA prototypeA = new ConcretePrototypeA();
prototypeA.X = 10;
prototypeA.Y = 20;

// Create a new instance of ConcretePrototypeB
ConcretePrototypeB prototypeB = new ConcretePrototypeB();
prototypeB.Name = "Bob";
prototypeB.Numbers = new List<int> { 1, 2, 3 };

// Use the clone method to create new instances of ConcretePrototypeA and ConcretePrototypeB
ConcretePrototypeA cloneA = (ConcretePrototypeA)prototypeA.Clone();
ConcretePrototypeB cloneB = (ConcretePrototypeB)prototypeB.Clone();
}
}

In this example, there is an abstract class Prototype which defines a Clone() method. The concrete classes ConcretePrototypeA and ConcretePrototypeB inherit from the Prototype class and provide their own implementation of the Clone() method.

In the Client class, we create an instance of ConcretePrototypeA and ConcretePrototypeB and set their properties. Then we use the Clone() method to create new instances of ConcretePrototypeA and ConcretePrototypeB respectively.

By using the Clone() method, we can create new instances of the objects without having to use the constructors, which can save time and resources.

There are also several different ways to implement the prototype pattern in C#, some of the most common include:

1. Using an abstract Clone() method

This is the most basic and widely used method, where an abstract Clone() method is defined in an interface or abstract class, and concrete classes that implement the interface or inherit from the abstract class provide their own implementation of the Clone() method. This method is used in the example that I provided earlier.

2. Using the ICloneable interface

C# provides a built-in ICloneable interface that can be used to implement the prototype pattern. The ICloneable interface defines a single Clone() method that must be implemented by any class that wants to support cloning.

class ConcretePrototypeA : ICloneable
{
public int X { get; set; }
public int Y { get; set; }

public object Clone()
{
// Create a new instance of ConcretePrototypeA and copy the values of X and Y
ConcretePrototypeA clone = new ConcretePrototypeA();
clone.X = this.X;
clone.Y = this.Y;
return clone;
}
}

class Client
{
public void Main()
{
ConcretePrototypeA prototypeA = new ConcretePrototypeA();
prototypeA.X = 10;
prototypeA.Y = 20;

// Use the Clone method to create a new instance of ConcretePrototypeA
ConcretePrototypeA clone = (ConcretePrototypeA)prototypeA.Clone();
}
}

In this example, ConcretePrototypeA class implements the ICloneable interface and provides an implementation for its Clone() method. The Clone() method creates a new instance of ConcretePrototypeA and copies the values of the properties, in this case X and Y.

The Client class creates an instance of ConcretePrototypeA and uses the Clone() method to create a new instance of ConcretePrototypeA.

By implementing the ICloneable interface, the ConcretePrototypeA class can be used in situations where a class or method is expecting an object that implements ICloneable, which makes the code more flexible and reusable.

3. Using the MemberwiseClone() method

C# also provides a built-in method called MemberwiseClone() that can be used to create a shallow copy of an object. This method can be used to implement the prototype pattern, but it only creates a shallow copy, which means that any reference types in the object will be copied by reference rather than by value.

class ConcretePrototypeA
{
public int X { get; set; }
public int Y { get; set; }
}

class Client
{
public void Main()
{
ConcretePrototypeA prototypeA = new ConcretePrototypeA();
prototypeA.X = 10;
prototypeA.Y = 20;

// Use the MemberwiseClone method to create a new instance of ConcretePrototypeA
ConcretePrototypeA clone = (ConcretePrototypeA)prototypeA.MemberwiseClone();
}
}

In this example, the ConcretePrototypeA class does not need to implement any specific interface or method to support cloning, it can use the built-in MemberwiseClone() method which is present in Object class. The MemberwiseClone() method creates a shallow copy of the object, which means that any reference types in the object will be copied by reference rather than by value.

In the Client class, we create an instance of ConcretePrototypeA and set its properties. Then we use the MemberwiseClone() method to create a new instance of ConcretePrototypeA.

It’s important to note that the MemberwiseClone() method creates a shallow copy, which means that any reference types in the object will be copied by reference rather than by value. This may not be suitable for all situations, and you may need to create a deep copy of the object instead.

4. Using the Serialization

In C#, the prototype pattern can be implemented using the Serialization technique. It is done by serializing the object and deserializing it to create a new instance.

class ConcretePrototypeA
{
public int X { get; set; }
public int Y { get; set; }
}

class Client
{
public void Main()
{
ConcretePrototypeA prototypeA = new ConcretePrototypeA();
prototypeA.X = 10;
prototypeA.Y = 20;

// Serialize the prototype object
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
formatter.Serialize(stream, prototypeA);
stream.Position = 0;

// Deserialize the prototype object to create a new instance
ConcretePrototypeA clone = (ConcretePrototypeA)formatter.Deserialize(stream);
}
}
}

In this example, the ConcretePrototypeA class does not need to implement any specific interface or method to support cloning. The prototype object is serialized using the BinaryFormatter class and written to a MemoryStream object. Then, the MemoryStream object is read back to deserialize the prototype object and create a new instance.

This method can be used to create deep copies of objects, since all the properties of the object are serialized along with its state. However, it can be relatively slow compared to the other methods and it requires all the properties of the object to be marked as Serializable.

It’s also important to note that this method can be used to clone complex object graphs, where an object contains other objects as properties, and those objects also contain other objects and so on.

5. Using the Copy Constructor

The prototype pattern can also be implemented by using a copy constructor, which is a special constructor that creates a new instance of a class by copying the values of the properties from an existing instance.

class ConcretePrototypeA
{
public int X { get; set; }
public int Y { get; set; }

public ConcretePrototypeA() { } // Parameterless constructor

public ConcretePrototypeA(ConcretePrototypeA prototype)
{
X = prototype.X;
Y = prototype.Y;
}
}

class Client
{
public void Main()
{
ConcretePrototypeA prototypeA = new ConcretePrototypeA();
prototypeA.X = 10;
prototypeA.Y = 20;

// Use the copy constructor to create a new instance of ConcretePrototypeA
ConcretePrototypeA clone = new ConcretePrototypeA(prototypeA);
}
}

The class ConcretePrototypeA has a default constructor which allows an object to be created without specifying any argument, and a copy constructor which takes an instance of ConcretePrototypeA as a parameter and creates a copy of it.

In this example, the ConcretePrototypeA class has a copy constructor that takes an instance of ConcretePrototypeA as a parameter. The copy constructor copies the values of the properties of the prototype object to the new object.

The Client class creates an instance of ConcretePrototypeA and sets its properties. Then it uses the copy constructor to create a new instance of ConcretePrototypeA.

This method can be useful when you need to create a deep copy of an object, since the constructor can recursively create copies of any objects that are properties of the original object. However, it can be relatively verbose, especially for complex object graphs, and it requires that the object be constructed using a constructor.

Overall, the choice of which way to use depends on the specific requirements of your application. Each method has its own advantages and disadvantages, and the best approach may be to use a combination of these methods.

Disadvantages

The prototype pattern has a few disadvantages, including:

  1. Complexity: Creating a prototype can be complex, especially when dealing with complex object graphs. This complexity can be mitigated by using a framework that automatically handles the cloning process.
  2. Limited flexibility: The prototype pattern can be limiting when it comes to creating new objects with different properties. It requires that the properties of the prototype object be set in advance, so it can’t be used to create objects with arbitrary properties.
  3. Performance: The prototype pattern can have a performance impact, especially when creating large numbers of objects or when the cloning process is computationally expensive.
  4. Incorrect implementation: If the Clone method is not implemented correctly, it can lead to unexpected behavior. For example, if the clone method does not create a deep copy of the object, it can lead to unexpected changes in the original object when the clone is modified.
  5. Code maintenance: When adding new fields to the prototype object, you will need to update the clone method accordingly. This can lead to increased maintenance costs.
  6. Limited control: The prototype pattern does not give you much control over the cloning process. You can’t specify how the properties of the prototype are copied to the new object, and you can’t add any validation or error handling to the cloning process.

Overall, while the prototype pattern can be a useful way to improve performance and simplify object creation, it’s important to weigh the advantages and disadvantages of the pattern, and to consider whether it’s the best approach for a given problem.

Conclusion

In conclusion, the prototype design pattern is a creational design pattern that allows creating new objects by copying an existing prototype object rather than creating new instances from scratch. It can be useful in scenarios where object creation is an expensive operation and creating multiple instances of the same object is needed. The prototype pattern can simplify object creation and improve performance by creating new objects by copying existing prototype objects.

However, it also has some disadvantages, such as complexity, limited flexibility, performance impact, and code maintenance. When considering whether to use the prototype pattern, it is important to weigh the advantages and disadvantages, and to consider whether it is the best approach for a given problem. Additionally, it is important to implement the Clone method correctly to avoid unexpected behavior.

Thanks for reading! If you found the article helpful, you can clap and follow. So you will notified of new articles.

# Reference

It was created with the help of ChatGPT AI.

--

--

Göksu Deniz
Göksu Deniz

Written by Göksu Deniz

Software Engineer, passionate about creating efficient solutions. Skilled in mentoring teams to deliver successful projects. Always exploring new tech trends.