Introduction
The Prototype pattern is considered to be used in the following situations
- Each time the object is created, resource intensive data are initialized which affects the performance, for example: calls third party API, gets the JSON response, converts it into POCO object and performs some operations and loads this data into the object.
- Assume that the same type of objects share the same resource and time consuming data, for example: In heavy database access, the same database
resultset
s are needed into all the objects(emp1
, emp2
,...emp<n>
) of the same type "Employee
". - The cloned object is noticeably less in state (properties values) changes compared to original object so that the cloned object data can be changed as per needs.
Note
While prototype pattern literally creates memory for object, the object pool patterns reuse the already created objects.
Prototype Pattern
- An object is instantiated by cloning the existing original object.
- This sophisticated original object is called Prototype object.
- An object to be cloned (
Prototype
) should provide a provision to clone (copy
) itself for other objects. - This provision is made possible by exposing the
Clone( )
method to the client (any calling code is called client, please do not correlate with the client/server idiom here). It is not mandatory to use the same name as Clone( )
, any convenient name can be used. - The client code need not use the "
new
" keyword to create new object. It just calls the Clone()
method in the Concrete Prototype Class
object. This Clone()
method just returns the reference variable of newly created cloned object with all data loaded. - C# provides the
IClonable
interface as Abstract Prototype which can be implemented in the Concrete Prototype Class
. IClonable
exposes clone() method, but the problem is that the return type of the Clone method is object
, so type casting is needed. - The state (property values) of the
Cloned
object remains the same as Prototype
object at the time it is being cloned. - If the
Prototype
class contains only primitive types, then Shallow Copy is enough. - If the
Prototype
Class holds inside any reference variable to other object, (for e.g., Employee
object holds Address
object inside), then Deep Cloning is needed, which means new memory is allocated for the reference object too. Otherwise, the Prototype
object and all cloned objects share the same memory for the referred objects. In our case, Address
. So any change in Address
object by any one object affects all other objects because all share the same memory address. - Each language provides a way to clone an object. In C#, this is carried out by
MemberwiseClone()
.
Participants
Prototype
: Declares method signature for cloning itself ConcretePrototype
: Method implementation of cloning the object itself Client
: The code that needs the cloned object and calls the ConcretePrototype
object's clone()
method
Background
Object Creation
Generally, an object is created for the class by the special keyword "new
".
The "new
" keyword does three things:
- Memory is created on the Heap.
- Data is initialized to this newly created object in the memory through constructor (Optional).
- The pointer or the reference to the object is returned.
For example:
Here, the objEmp
is the reference variable which holds the address or reference to the actual object which is residing in the Heap Memory. This reference variable objEmp
stays in the Stack Memory.
Resource Expensive Object
Assume that an object "objEmp
" holds huge data which has been collected from many resources and complex processing, which has involved heavy performance hits. Now the same data is needed for other objects too for example "objEmp1
","objEmp2
".."objEmp<n>
". These objects should not undergo the same sufferings like the "objEmp
" object as the required data is already held in the source object "objEmp
". The "objEmp
" should be cloned.
Implementing the Prototype Pattern
Manual Cloning
Clone every fields, properties , referece types manually. The problem is that this may take longer time to clone all reference objects, and reference objects inside reference objects and goes on. This method is not advisable to implement.
public class Person : ICloneable
{
public string Name;
public Person Spouse;
public object Clone()
{
Person p = new Person();
p.Name = this.Name;
if (this.Spouse != null)
p.Spouse = (Person)this.Spouse.Clone();
return p;
}
}
Two Types of cloning
MemberwiseClone()
As per Microsoft 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."
Shallow Copy
When shallow copy is made to create the clone object from the source object, new memory for the cloned object is created only for value types. If the reference variable is inside the source object, then both the original and the cloned object share the same memory for reference variable object. Any change in value of reference object state affect both the original and cloned object.
Generally, this type of shallow copy of object is accomplished using MemberwiseClone().
Deep Copy
When deep copy is performed to create the cloned object, separate memory is created for child reference variables along with primitive types. To accomplish that, once again, MemberwiseClone() keyword is should be implemeted and exposed in all the reference classes to create the child reference object and then it is assigned to the cloned object so that new separate memory is created for child reference variable too. This is one way to implement Deep Copy of object with reference types members. There are other ways like Serializatiton, using third party NewtonSoft JsonConvert, Reflection..etc
Example using MemberwiseClone()
public interface IEmployee
{
IEmployee ShallowClone();
IEmployee DeepClone();
string GetDetails();
}
public interface IProject
{
IProject ShallowClone();
}
public class ProjectDetail : IProject
{
public string ProjectName { get; set; }
public int Size { get; set; }
public IProject ShallowClone()
{
return (IProject)MemberwiseClone();
}
}
public class Developer : IEmployee
{
public string Name { get; set; }
public ProjectDetail project { get; set; }
public IEmployee ShallowClone()
{
return (IEmployee)MemberwiseClone();
}
public IEmployee DeepClone()
{
Developer dev = (Developer)this.ShallowClone();
dev.project = (ProjectDetail)project.ShallowClone();
return dev;
}
public string GetDetails()
{
return string.Format("{0}->Project Name:{1}->Project Size:{2}",
Name,
project.ProjectName,
project.Size);
}
}
class PrototypeClient
{
static void Main(string[] args)
{
Console.WriteLine("Shallow Copy Example:");
Developer dev1 = new Developer()
{
Name = "Joe",
project = new ProjectDetail()
{
ProjectName = "E-Commerce",
Size = 10
}
};
Developer devCopy1 = (Developer)dev1.ShallowClone();
devCopy1.Name = "Sam";
devCopy1.project.ProjectName = "Mobile App";
devCopy1.project.Size = 8;
Console.WriteLine(dev1.GetDetails());
Console.WriteLine(devCopy1.GetDetails());
Console.WriteLine("\nDeep Copy Example:");
Developer dev2 = new Developer()
{
Name = "Joe",
project = new ProjectDetail()
{
ProjectName = "E-Commerce",
Size = 10
}
};
Developer devCopy2 = (Developer)dev2.DeepClone();
devCopy2.Name = "Sam";
devCopy2.project.ProjectName = "Mobile App";
devCopy2.project.Size = 8;
Console.WriteLine(dev2.GetDetails());
Console.WriteLine(devCopy2.GetDetails());
Console.ReadKey();
}
}
Output
Explanation
In this program:
IEmployee
is the Prototype
Developer
is the ConcretePrototype
PrototypeClient
is the Client
which uses the Prototype
to create objects
In the above program, both the shallow copy and deep copy is implemented. The developer class holds the ProjectDetail
object as a reference variable. This is called object composition(has - a relationship). The original object dev1
holds name="joe"
, and it also has the reference variable "project
" pointing to the ProjectDetail
object. The values for the fields are assigned when declaring the dev1
object. The shallow copy is made from dev1
object (original object) and is assigned to new object devCopy1
. All values of the devCopy1
are changed purposefully. As the reference object inside the dev1
and devCopy1
both share the same memory location, the last modified value of the reference object values are set to both the original and cloned object devCopy1
. In deep copy mode, the "new
" reference object (child object) is created to allocate separate memory for the reference child object "project
" for the cloned object.
Deep Clone using Serializatiton
Serialization : Converting the object into binary stream
De-serialization : Converting back from the binary stream to the original object
No need to worry about each and every reference child members cloning.
Example
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace CreationPattern
{
public class Utility
{
public static T DeepClone<T>(T sourceObj)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("Class must be marked [Serializable]");
}
if (Object.ReferenceEquals(sourceObj, null))
{
return default(T);
}
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, sourceObj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
[Serializable]
public class ProjectDetail
{
public string ProjectName { get; set; }
public int Size { get; set; }
}
[Serializable]
public class Developer
{
public string Name { get; set; }
public ProjectDetail project { get; set; }
public Developer DeepClone()
{
return (Developer)Utility.DeepClone<Developer>(this);
}
public string GetDetails()
{
return string.Format("{0}->Project Name:{1}->Project Size:{2}",
Name,
project.ProjectName,
project.Size);
}
}
class PrototypeClient
{
static void Main(string[] args)
{
Console.WriteLine("\nDeep Copy Example:");
Developer dev2 = new Developer()
{
Name = "Joe",
project = new ProjectDetail()
{
ProjectName = "E-Commerce",
Size = 10
}
};
Developer devCopy2 = (Developer)dev2.DeepClone();
devCopy2.Name = "Sam";
devCopy2.project.ProjectName = "Mobile App";
devCopy2.project.Size = 8;
Console.WriteLine(dev2.GetDetails());
Console.WriteLine(devCopy2.GetDetails());
Console.ReadKey();
}
}
}
Output
Explanation
Stream is series of bytes. MemoryStream is series of bytes, which is going to be dealt with Memory. In case the the serialized object has to be stored in a File and deserialized to the original object, then FileStream can be the choice.
The Source Object is serialized using the BinaryFormatter object's method Serialize() and stored in the form of MemoryStream object. And BinaryFormatter uses its Deserialize() to deserialize the MemoryStream obj and returns the new object.
Deep Clone using NewtonSoft JSON Serialization
Import using Newtonsoft.Json in your project. Use NuGet if do not have it
using Newtonsoft.Json;
using System;
namespace CreationPattern
{
public class Utility
{
public static T DeepClone<T>(T sourceObj)
{
if (Object.ReferenceEquals(sourceObj, null))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(sourceObj));
}
}
public class ProjectDetail
{
public string ProjectName { get; set; }
public int Size { get; set; }
}
public class Developer
{
public string Name { get; set; }
public ProjectDetail project { get; set; }
public Developer DeepClone()
{
return (Developer)Utility.DeepClone<Developer>(this);
}
public string GetDetails()
{
return string.Format("{0}->Project Name:{1}->Project Size:{2}",
Name,
project.ProjectName,
project.Size);
}
}
class PrototypeClient
{
static void Main(string[] args)
{
Console.WriteLine("\nDeep Copy Example:");
Developer dev2 = new Developer()
{
Name = "Joe",
project = new ProjectDetail()
{
ProjectName = "E-Commerce",
Size = 10
}
};
Developer devCopy2 = (Developer)dev2.DeepClone();
devCopy2.Name = "Sam";
devCopy2.project.ProjectName = "Mobile App";
devCopy2.project.Size = 8;
Console.WriteLine(dev2.GetDetails());
Console.WriteLine(devCopy2.GetDetails());
Console.ReadKey();
}
}
}
Output
Explanation
(Developer)Utility.DeepClone<Developer>(this)
This code calls the generic Utility class static method. This method uses the JsonConvert.Serialize () to serialize the object into JSON string and JsonConvert.Deserialize() deserializes the JSON string into new object and returns to the called code.
Note to all readers:
Dear programmers, this is my first article. My goal is to make this Prototype Pattern very clear to you with simple language, but not compromising the in-depth technical details. Please share your feedback. Many design patterns like this are yet to come. Thanks for reading!