When swimming through the powerful ocean of objects and classes, you will easily find that few works have been able to transform the tide as successfully as the design patterns laid out by the Gang of Four (GoF).
These structures help solve many of the problems object-oriented developers face by laying out a framework for writing efficient object-oriented code. While the GoF laid out several helpful models, we will be focusing on the Prototype Creational Design Pattern.
The Prototype pattern is a creational design pattern. You can use it when a prototype instance determines the type of object you need to create. Then, that prototypical instance is cloned to create new instances of the same type.
This post will walk you through why this pattern is essential, its benefits, and how you can apply it to real-world scenarios.
What is the Prototype design pattern?
The Prototype pattern is a creational design pattern that is used to instantiate new objects by copying an existing object. The prototype object serves as a template from which you can create new objects. You can use this pattern when the cost of creating and initializing a new object is high and when it is not desirable to create a new object each time one is needed.
To use this pattern, you must first create a prototype object. You can then use this prototype object to create new objects by calling the Clone() method. The prototype object can be of any type, including an abstract class or interface. However, when using this pattern, it is vital to ensure that the prototype object is initialized correctly before it is used to create new objects. Otherwise, the objects created by the Clone() method will inherit the uninitialized state of the prototype object.
Although you can use prototypes for an extensive range of situations, they often shine brightest when you are faced with a need to create multiple copies of a complex object. For example, let’s say you need to create a new instance of the Student class. But to create a new Student, you first need to create a Human object, then call all the necessary methods to initialize the Student class fully.
While crafting one Student object may feel simple, doing this multiple times creates more of a headache. This strain is mostly because making multiple objects like this is expensive and time-consuming, leading to slow and tedious development. Fortunately, this is one of the problems that using the Prototype design pattern solves. In this case, rather than making a new Student instance, you clone it instead.
Other creational design patterns are:
- Abstract Factory – it is useful when you need to create families of related objects without specifying their concrete classes.
- Builder – use it when you need to construct complex objects step by step. I have a complete guide to mastering it.
- Factory Method – use it to create objects based on a set of specific criteria, but you don’t know in advance what those criteria will be.
- Singleton – ensures that you can create only one instance of an object. Some people say the Singleton pattern is an anti-pattern. But I asked 888 developers whether or not that’s true. And here’s what they responded.
When would you use the Prototype design pattern?
There are several use-cases of the Prototype design pattern.
#1 When new instantiations have only a few different combinations of state
It is common in OOP to create several instances of the same object. However, while crafting each new instance, you may quickly discover that they all share similar attributes. In such cases, you can write more efficient code using the Prototype pattern instead of instantiation using the new keyword.
This creational pattern will allow you to quickly create multiple instances that share the same attributes instead of laboring over assigning the same values to each instance. Additionally, by using the Prototype pattern, once you are confident that the attributes of the existing instance are accurate, you know for sure that all clones will be also correct.
#2 When creating a new object is going to be costly
Delivering quick and efficient programs should always be the core goal of software development.
However, to achieve the lightning speed, the users have become increasingly desperate for, you often need to discover new ways to trim out any slowness you find. Fortunately, this is an area in which object cloning and the Prototype design pattern excel.
For example, imagine that you need to call a database multiple times every time you need to create a new object. In such cases, instead of calling your database all the time, you create a clone and cache it. Then, the next time your user chooses to request this object, you can comfortably serve them the cached version.
With prototyping, you will only need to update your database when the data changes. This lazy method should reduce the number of queries you make throughout your project’s lifetime, making your application appear smoother.
#3 To avoid building a hierarchy of factories similar to the hierarchy of pre-defined products
The Prototype pattern can help avoid building a class hierarchy of factories that parallels the class hierarchy of the objects being created. That kind of behavior is known as the parallel inheritance hierarchies code smell. However, it increases the maintenance cost because every time you create a subclass in one hierarchy (or implement one interface), you also need to create another class in another hierarchy.
Using the Prototype design pattern makes it possible to create a prototype of an object and then create new instances by cloning the prototype. This can be simpler than maintaining a separate hierarchy of factories for each type of object that needs to be created.
How to implement the Prototype design pattern in C# – Structural code
The following class diagram shows the sample implementation of the Prototype design pattern in C#.
The participants in this pattern are:
- Prototype – the Prototype (or IPrototype) declares an interface for cloning.
- ConcretePrototype – this class implements the Prototype interface.
- Client – creates a new object by calling the Clone method.
Let’s see the sample code of the Prototype design pattern in C#:
public interface IPrototype
{
string Name { get; set; }
IPrototype Clone();
}
public class ConcretePrototype1 : IPrototype
{
public ConcretePrototype1(string name)
{
Name = $"{name} cloned from ConcretePrototype1";
}
public string Name { get; set; }
public IPrototype Clone()
{
return (IPrototype)this.MemberwiseClone();
}
}
public class ConcretePrototype2 : IPrototype
{
public ConcretePrototype2(string name)
{
Name = $"{name} cloned from ConcretePrototype2";
}
public string Name { get; set; }
public IPrototype Clone()
{
return (IPrototype)this.MemberwiseClone();
}
}
And the usage:
ConcretePrototype1 prototype1 = new ConcretePrototype1("my first prototype");
var clone1 = prototype1.Clone();
Console.WriteLine("After cloning prototype1, my clone name is: " + clone1.Name);
ConcretePrototype2 prototype2 = new ConcretePrototype2("my second prototype");
var clone2 = prototype2.Clone();
Console.WriteLine("After cloning prototype 2, my clone name is: " + clone2.Name);
Console.ReadKey();
Code output:
After cloning prototype1, my clone name is: my first prototype cloned from ConcretePrototype1
After cloning prototype 2, my clone name is: my second prototype cloned from ConcretePrototype2
This code follows the typical interface implementation hierarchy. The code defines two concrete classes for the abstract prototype interface. Each class that implements the IPrototype interface has to implement the Clone method.
In the above case, the implementation uses the Object.MemberwiseClone method to create a shallow copy of the object. As per official documentation:
The MemberwiseClone method creates a shallow copy by creating a new object and then copying the nonstatic fields of the current object to the new object. If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object.
To summarize, the copied object will hold the same reference to the field if the field is a reference type. If you need to perform a deep copy, then you need to implement a custom Clone method. But in many cases, using the MemberwiseClone method to get a new shallow copy will be just enough.
Prototype design pattern – Real-world example in C#
Using our knowledge of prototypes, you can now apply Prototypes to the student scenario I have mentioned earlier.
public interface IStudentPrototype
{
IStudentPrototype Clone();
}
public class Student : IStudentPrototype
{
public Student(int age, string classroom, string housegroup)
{
this.Age = age;
this.Classroom = classroom;
this.Housegroup = housegroup;
}
public int Age { get; set; }
public string Classroom { get; set; }
public string Housegroup { get; private set; }
// override clone to return a shallow copy
public IStudentPrototype Clone()
{
Console.WriteLine($"Cloning Alpha Student of the House {Housegroup}. " +
$"Belonging to Classroom {Classroom}. {Age} years of age.");
return (IStudentPrototype)this.MemberwiseClone();
}
}
public class StudentManager
{
private Dictionary<string, IStudentPrototype> students =
new Dictionary<string, IStudentPrototype>();
// Indexer
public IStudentPrototype this[string key]
{
get { return students[key]; }
set { students.Add(key, value); }
}
}
And the usage:
StudentManager studentmanager = new StudentManager();
// initialise with first set of students
studentmanager["Rickon"] = new Student(13, "13A", "Stark");
studentmanager["Joffrey"] = new Student(13, "13B", "Lannister");
studentmanager["Walder"] = new Student(12, "13C", "Frey");
studentmanager["Arya"] = new Student(12, "13A", "Stark");
// make clones of these students:
var newStark = studentmanager["Rickon"].Clone();
var newStark2 = studentmanager["Rickon"].Clone();
var newLanniester = studentmanager["Joffrey"].Clone();
Console.ReadKey();
Code Output:
Cloning Alpha Student of the House Stark. Belonging to Classroom 13A. 13 years of age.
Cloning Alpha Student of the House Stark. Belonging to Classroom 13A. 13 years of age.
Cloning Alpha Student of the House Lannister. Belonging to Classroom 13B. 13 years of age.
Instead of making a new student instance each time a new Stark, Lannister, or Frey child joins an existing classroom, you make a clone of them instead. This duplication reduces the overall overhead in the codebase and squashes redundancies.
In this case, by using the Prototype design pattern, adding similar students to the same house becomes more efficient, providing a boost that makes it easy for teachers to fill up their classrooms.
Benefits of Using the Prototype Design Pattern in Your Codebase
There are several benefits of using the Prototype design pattern:
- Adding and removing classes at run-time
- Specify new objects by varying values or structure
- Reduced subclassing
- Dynamic configuration
Adding and removing classes at run-time
Prototypes let you incorporate new concrete product classes into your client system by registering a prototype. That’s quite a flexible pattern and has an added benefit that you can install and remove the client prototypes at run-time.
Specify new objects by varying values or structure
Speed is one of the most paramount aspects of software development. All around the world, programmers are always looking for efficient ways to help them deliver their code in record time. This need for speed shows why it is hardly a surprise that prototypes have become a core aspect of Object-Oriented Programming in C#.
Considering the student example from earlier, you may want to fill up a classroom with 25 new-born Frey students. Naturally, as all the children will be in the same age group, you can assume their classroom attributes will be the same. So, using the Prototype pattern, you need to instantiate the first Frey child, then call the Clone() method for each new child.
On the other hand, if you chose to instantiate each child manually, you would have found yourself inputting the same attributes each time. This alternative method is much slower and laborious. Additionally, you may quickly find that your code will be more prone to bugs when manually instantiating with the same values.
Reduced subclassing
The Factory Method is usually a good fit for projects that are made up of a hierarchy of objects. The Prototype pattern allows you to clone an existing object instead of asking the factory to create a new instance. The Prototype pattern eliminates the need to introduce subclassing for creating products.
Dynamic configuration
This design pattern is also excellent at trimming the amount of boilerplate code you use in your application.
By combining this design pattern with deep copying, your original instance can act as the boilerplate, allowing you to modify the copies. After cloning your first student instance, you can safely make any changes to the clone’s details. For example, you can make a clone of a student that initially belonged to 12A class but effortlessly modify their class to become 15A.
What are related patterns to the Prototype design pattern?
Related design patterns to the Prototype pattern are Abstract Factory, Composite, and the Decorator patterns. Abstract Factory might store a set of prototypes and use them to clone and return new product objects. The Composite and the Decorator patterns can benefit from using the Prototype pattern in the code.
Conclusion
Patterns are essential in life.
From the patterns of nature that we see all around us to the patterns we create as humans, these recurring structures help us make sense of our world.
In software design, design patterns are one of the most important skills you can learn.
The Prototype design pattern is an essential tool for object-oriented programming. It allows objects to create new objects without using the class constructor. This can be especially helpful when creating many similar objects or when you want to avoid the overhead of creating a new object instance on every call.
In this post, you saw how you can use the Prototype pattern in your own code with some practical examples.
If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.