.Net, Software Architecture

Prototype Pattern using C#

Introduction

According to Wikipedia:

The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects.

According to Gang Of Four:

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

UML:

600px-Prototype_UML.svg

When to use it?

  1. To avoid the inherent cost of creating a new object in the standard way (e.g., using the ‘new’ keyword) when it is prohibitively expensive for a given application.
  2. Hide concrete classes from the client.
  3. Add and remove new classes (via prototypes) at runtime.
  4. Keep the number of classes in the system to a minimum.
  5. Adapt to changing structures of data at runtime.

The main players in the pattern are:

IPrototype
Defines the interface that says prototypes must be cloneable

Prototype
A class with cloning capabilities

PrototypeManager
Maintains a list of clone types and their keys

Client
Adds prototypes to the list and requests clones

Example in C#

PrototypeBase.cs

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace DesignPatterns.PrototypePattern {
[Serializable]
public abstract class PrototypeBase<T> {

// Shallow clone
public T Clone() {
return (T)this.MemberwiseClone();
}

//Deep Clone
public T DeepClone() {
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
T copy = (T)formatter.Deserialize(stream);
stream.Close();
return copy;
}
}
}

Prototype.cs

using System;

namespace DesignPatterns.PrototypePattern {
[Serializable]
public class Prototype : PrototypeBase<Prototype> {
// Content members
public string Country { get; set; }
public string Capital { get; set; }
public DeeperData Language { get; set; }

public Prototype(string country, string capital, string language) {
Country = country;
Capital = capital;
Language = new DeeperData(language);
}

public override string ToString() {
return Country + "\t\t" + Capital + "\t\t->" + Language;
}
}
}

DeeperData.cs

using System;

namespace DesignPatterns.PrototypePattern {
[Serializable]
public class DeeperData {
public string Data { get; set; }
public DeeperData(string s) {
Data = s;
}
public override string ToString() {
return Data;
}
}
}

PrototypeManager.cs

using System.Collections.Generic;

namespace DesignPatterns.PrototypePattern {
public class PrototypeManager {
public Dictionary<string, Prototype> prototypes
= new Dictionary<string, Prototype> {
{"Germany",
new Prototype("Germany", "Berlin", "German")},
{"Italy",
new Prototype("Italy", "Rome", "Italian")},
{"Australia",
new Prototype("Australia", "Canberra", "English")}
};
}
}

PrototypeClient.cs

using System;

namespace DesignPatterns.PrototypePattern {
public class PrototypeClient : IDesignPattern {

private void Report(string s, Prototype a, Prototype b) {
Console.WriteLine("\n" + s);
Console.WriteLine("Prototype " + a + "\nClone      " + b);
}

public void Run() {
Console.WriteLine("\n------------------Prototype Pattern------------------");
PrototypeManager manager = new PrototypeManager();
Prototype c2, c3;

// Make a copy of Australia's data
c2 = manager.prototypes["Australia"].Clone();
Report("Shallow cloning Australia\n===============",
manager.prototypes["Australia"], c2);

// Change the capital of Australia to Sydney
c2.Capital = "Sydney";
Report("Altered Clone's shallow state, prototype unaffected",
manager.prototypes["Australia"], c2);

// Change the language of Australia (deep data)
c2.Language.Data = "Chinese";
Report("Altering Clone deep state: prototype affected *****",
manager.prototypes["Australia"], c2);

// Make a copy of Germany's data
c3 = manager.prototypes["Germany"].DeepClone();
Report("Deep cloning Germany\n============",
manager.prototypes["Germany"], c3);

// Change the capital of Germany
c3.Capital = "Munich";
Report("Altering Clone shallow state, prototype unaffected",
manager.prototypes["Germany"], c3);

// Change the language of Germany (deep data)
c3.Language.Data = "Turkish";
Report("Altering Clone deep state, prototype unaffected",
manager.prototypes["Germany"], c3);
}
}
}

Output:

Shallow cloning Australia
===============
Prototype Australia             Canberra                ->English
Clone      Australia            Canberra                ->English

Altered Clone’s shallow state, prototype unaffected
Prototype Australia             Canberra                ->English
Clone      Australia            Sydney          ->English

Altering Clone deep state: prototype affected *****
Prototype Australia             Canberra                ->Chinese
Clone      Australia            Sydney          ->Chinese

Deep cloning Germany
============
Prototype Germany               Berlin          ->German
Clone      Germany              Berlin          ->German

Altering Clone shallow state, prototype unaffected
Prototype Germany               Berlin          ->German
Clone      Germany              Munich          ->German

Altering Clone deep state, prototype unaffected
Prototype Germany               Berlin          ->German
Clone      Germany              Munich          ->Turkish

So whats happening here?

Firstly we are using two ways to clone the object

  1. Shallow Clone: In .Net we have a method called MemberwiseClone, its a method that is available on all objects. It copies the values of all fields and any references, and returns a reference to this copy. However, it does not copy what the references in the object point to.Many objects are simple, without references to other objects, and therefore shallow copies are adequate.To preserve the complete value of the object, including all its subobjects use a deep copy.
  2. Deep Clone: An alternative is a deep copy, meaning that fields are dereferenced: rather than references to objects being copied, new copy objects are created for any referenced objects, and references to these placed in B. The result is different from the result a shallow copy gives in that the objects referenced by the copy B are distinct from those referenced by A, and independent. Deep copies are more expensive, due to needing to create additional objects, and can be substantially more complicated, due to references possibly forming a complicated graph. In the .NET Framework they are encapsulated in a process called serialization, which can be defined by using attribute [Serializable] on the class to be serialized. Objects are copied to a given destination and can be brought back again at will. The options for serialization destinations are several, including disks and the Internet, but the easiest one for serializing smallish objects is memory itself. Thus a deep copy consists of serializing and deserializing in one method.

Then we are also using a PrototypeManager, which I mentioned earlier that its used to maintains a list of clone types and their keys so that we can retrieve an already cloned object quickly.

The main program consists of a series of experiments demonstrating the effects of cloning and deep copying.

In the first group, Australia is shallow copied. After changing of Australia’s clone, the capital is Canberra in the prototype and Sydney in the clone. The statement responsible is   c2.Capital = “Sydney”;

however, changing the language to Chinese (  c2.Language.Data = “Chinese”;) also changes the prototype’s language to Chinese. That is not what we wanted. We got the error because we did a shallow copy and the language in the prototype and in the clone reference the same DeeperData object.

In the next experiment, we clone Germany using a deep copy ( c3 = manager.prototypes[“Germany”].DeepClone();).

Now changing the capital ( c3.Capital = “Munich”;) does not affect the prototype but the deep clone is affected. Similarily  altering the deep state-its language to Turkish ( c3.Language.Data = “Turkish”;), shows the prototype after the changes; it is unaffected.

That’s all about Prototype Pattern.

Code can be found at GitHub

Reference: