In this post, we will learn about the third pattern in Creational Design Patterns - Prototype. Let's see what is the Prototype Pattern, when and how we implement it in C# and .Net.
Note: Code can be downloaded at my Github
1. What is the Prototype Design Pattern?
With the Prototype Design Pattern, programs are able to create a new instance of a class from an existing object. By doing so, it's easier to create an instance of a complex object that maybe costly to create a new one.
Prototype Design Pattern specifies the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
2. When to use Prototype Design Pattern
For example, in a car manufacturing industry, it would be difficult to produce a car from scratch. Rather than that, car companies usually make a clone from a previous car model and enhance it to produce a new and better car model every year. The same is apply to your programs if there is a complex class which is costly (in term of time and space) to be newly initiated.
Let's consider below example when you may want to create a new object by copying from an existing object. We have a Person and an Address class like below:
public class Person { public string Name { get; set; } public Address Address { get; set; } public Person(string name, Address address) { Name = name; Address = address; } public override string ToString() { return $"Name: {Name}, Address: {Address.Street}, {Address.City} - {Address.PostalCode.ToString()}"; } } public class Address { public string Street { get; set; } public string City { get; set; } public int PostalCode { get; set; } public Address(string street, string city, int postalCode) { Street = street; City = city; PostalCode = postalCode; } }
Now, we will create 2 objects from the Person class. The 2 objects are from the same household so the only different is their names.
class Program { static void Main(string[] args) { Person person1= new Person("Lucas", new Address("Avenue O", "Brooklyn", 11204)); Person person2= person1; person2.Name = "Eric"; Console.WriteLine(person1.ToString()); Console.WriteLine(person2.ToString()); } }
The above code will print out both people with the name Eric which is incorrect. The reason is the program just copies the reference (shallow copy) of the first person object to create the second one. We should implement Prototype Pattern by using ICloneable or Copy Constructor to make things work.
3. Implementing Prototype Design Pattern by using ICloneable
In the Person class, we can inherit the ICloneable interface which then can implement the Clone method. The Clone method creates a new object that is a copy of the current instance.public class PersonWithICloneable : ICloneable { public string Name { get; set; } public AddressWithICloneable Address { get; set; } public PersonWithICloneable(string name, AddressWithICloneable address) { Name = name; Address = address; } public override string ToString() { return $"Name: {Name}, Address: {Address.Street}, {Address.City} - {Address.PostalCode.ToString()}"; } public object Clone() { return this.MemberwiseClone(); } } public class AddressWithICloneable : ICloneable { public string Street { get; set; } public string City { get; set; } public int PostalCode { get; set; } public AddressWithICloneable(string street, string city, int postalCode) { Street = street; City = city; PostalCode = postalCode; } public object Clone() { return this.MemberwiseClone(); } }
Now, we will create 2 objects from the PersonWithICloneable class. The cloned person object will be updated with a different name and street address.
class Program { static void Main(string[] args) { PersonWithICloneable personWithICloneable1 = new PersonWithICloneable("Lucas", new AddressWithICloneable("Avenue O", "Brooklyn", 11204)); PersonWithICloneable personWithICloneable2 = (PersonWithICloneable)personWithICloneable1.Clone(); personWithICloneable2.Name = "Eric"; personWithICloneable2.Address.Street = "Sethlow"; Console.WriteLine(personWithICloneable1.ToString()); Console.WriteLine(personWithICloneable2.ToString()); } }
The result show that the 2 people have different names now. However, it seems like the street address is shallow copied again! This is a problem with using ICloneable. Let's take a look at how we resolve this problem by using a copy constructor.
4. Implementing Prototype Design Pattern by using Copy Constructor
Create PersonWithCopyConstructor and AddressWithCopyConstructor classes like below:
public class PersonWithCopyConstructor { public string Name { get; set; } public AddressWithCopyConstructor Address { get; set; } public PersonWithCopyConstructor(string name, AddressWithCopyConstructor address) { Name = name; Address = address; } public PersonWithCopyConstructor(PersonWithCopyConstructor personWithCopyConstructor) { Name = personWithCopyConstructor.Name; Address = new AddressWithCopyConstructor(personWithCopyConstructor.Address); } public override string ToString() { return $"Name: {Name}, Address: {Address.Street}, {Address.City} - {Address.PostalCode.ToString()}"; } } public class AddressWithCopyConstructor { public string Street { get; set; } public string City { get; set; } public int PostalCode { get; set; } public AddressWithCopyConstructor(string street, string city, int postalCode) { Street = street; City = city; PostalCode = postalCode; } public AddressWithCopyConstructor(AddressWithCopyConstructor address) { Street = address.Street; City = address.City; PostalCode = address.PostalCode; } }
Now, we will do something similar to update the second person name and street address.
class Program { static void Main(string[] args) { PersonWithCopyConstructor personWithCopyConstructor1 = new PersonWithCopyConstructor("Lucas", new AddressWithCopyConstructor("Avenue O", "Brooklyn", 11204)); PersonWithCopyConstructor personWithCopyConstructor2 = new PersonWithCopyConstructor(personWithCopyConstructor1); personWithCopyConstructor2.Name = "Eric"; personWithCopyConstructor2.Address.Street = "Sethlow"; Console.WriteLine(personWithCopyConstructor1.ToString()); Console.WriteLine(personWithCopyConstructor2.ToString()); } }
This time, it's print out the information correctly for both persons. It's safe to implement Prototype Pattern by using a Copy Constructor.
Great! So we have learnt another pattern in Creational Category. I hope this is helpful! Please let me know your thoughts by commenting in the section below! See you next time!