Summary: in this tutorial, you’ll learn how to use the C# Prototype pattern to create new objects by cloning existing ones.
Introduction to the C# Prototype Pattern
In C#, classes are blueprints for creating new objects. From classes, you use the new
operator to create an object.
The Prototype pattern is a creational design pattern that allows you to create new objects by cloning existing objects. In other words, the Prototype pattern allows you to create new objects from existing ones by cloning them without specifying the exact class of the objects.
The Prototype pattern is useful when you need to create new objects that are similar to existing ones but with some differences in their state or behavior.
The Prototype pattern is also helpful for creating a new object that is complex and may incur overhead. So you want to avoid the complexity and the overhead by cloning an existing object instead of creating a new one from scratch.
To implement the Prototype pattern, you follow these steps:
- First, define an interface or abstract class that specifies a method for cloning objects.
- Second, create concrete classes that implement the interface or extend the abstract class. Each concrete class represents a specific object type that can be copied.
To create a new object, you call the Clone()
method on the prototype object first and then modify the properties of the cloned object.
The following UML diagram illustrates how the Prototype pattern works.
The Prototype pattern involves the following participants:
- Prototype: The Prototype can be an interface or an abstract class. The Prototype interface (or abstract class) has the
Clone()
method that creates a new object from an existing object. - ConcretePrototype: This is a concrete class of the Prototype interface. The
ConcretePrototype
class implements theClone()
method for cloning an object. - Client: This is the client code that uses the Prototype interface for creating new objects. The client creates a new object by cloning an existing object and then customizes the cloned object as needed.
C# Prototype pattern example
The following C# program demonstrates how to use the Prototype pattern by creating a Warrior prototype and cloning it to create a new warrior with some differences in its properties:
using static System.Console;
namespace PrototypePattern;
public interface IPrototype<T>
{
T Clone();
}
public class Warrior : IPrototype<Warrior>
{
public string Name { get; set; }
public int Health { get; set; }
public int AttackPower { get; set; }
public Warrior(string name, int health, int attackPower)
{
Name = name;
Health = health;
AttackPower = attackPower;
}
public override string ToString()
=> $"Name: {Name}, Health:{Health}, AttackPower: {AttackPower}";
public Warrior Clone() => (Warrior)MemberwiseClone();
}
public class Client
{
public static void Main(string[] args)
{
// Create an instance of the Warrior prototype
var loki = new Warrior("Loki", 100, 20);
// Clone the Loki warrior to create
// a new warrior named Thor
var thor = loki.Clone();
thor.Name = "Thor";
thor.Health = 120;
// Now we have two different warrior objects with
// similar properties but with some differences
WriteLine(loki);
WriteLine(thor);
}
}
Code language: C# (cs)
Output:
Name: Loki, Health:100, AttackPower: 20
Name: Thor, Health:120, AttackPower: 20
Code language: plaintext (plaintext)
How it works.
First, define an interface called IPrototype
with a generic type parameter T
. The Prototype<T>
has a method called Clone()
that will be implemented by the concrete prototype for cloning an object:
public interface IPrototype<T>
{
T Clone();
}
Code language: C# (cs)
Second, define a concrete class Warrior
that implements the IPrototype
interface. The Warrior
class has three properties Name
, Health
, and AttackPower
. Also, it has a constructor to initialize these properties:
public class Warrior : IPrototype<Warrior>
{
public string Name { get; set; }
public int Health { get; set; }
public int AttackPower { get; set; }
public Warrior(string name, int health, int attackPower)
{
Name = name;
Health = health;
AttackPower = attackPower;
}
public override string ToString()
=> $"Name: {Name}, Health:{Health}, AttackPower: {AttackPower}";
public Warrior Clone() => (Warrior)MemberwiseClone();
}
Code language: C# (cs)
The Warrior class overrides the ToString()
method to return a string representation of the warrior object.
The Warrior class also implements the
method. The Clone()
method uses the Clone()
MemberwiseClone()
method that creates a shallow copy of the Warrior
object.
Note that the MemberwiseClone
method creates a shallow copy of an object by creating a new object and then copying the nonstatic fields of the object to the new object. If a field is a value type, it performs a bit-by-bit copy of the field. If a field is a reference type, it copies only the reference.
Since the properties of the Warrior
class are value types (except for the string
, which is a reference type but a special case), the shallow copy is fine in this case.
Third, define the Client
class with the Main()
method as the main entry point of the program:
public class Client
{
public static void Main(string[] args)
{
// Create an instance of the Warrior prototype
var loki = new Warrior("Loki", 100, 20);
// Clone the Loki warrior to create
// a new warrior named Thor
var thor = loki.Clone();
thor.Name = "Thor";
thor.Health = 120;
// Now we have two different warrior objects with
// similar properties but with some differences
WriteLine(loki);
WriteLine(thor);
}
}
Code language: C# (cs)
In the Main()
method, we create a new instance of the Warrior
prototype with the name Loki
, health 100
, and attack power 20
. Then we clone the Loki
warrior using the Clone()
method to create a new warrior named Thor
.
The Thor
warrior has the same properties as the Loki
warrior, except for the Name
and Health
properties, which are set to Thor
and 120
, respectively.
The benefit of using the Prototype pattern is that you can create new warriors with similar properties to an existing one without explicitly specifying the Warrior
class. Hence, the Prototype pattern offers flexibility and reduces code duplication.
Summary
- Use the Prototype pattern to create a new object by cloning an existing one without specifying the objects’ class explicitly.
- Use the
MemberwiseClone
method to create a shallow copy of the current object.