The Prototype pattern is a creational pattern in which objects are created using a prototypical instance of said object. This pattern is particularly useful for creating lots of instances of an object, all of which share some or all of their values.
The typical way of thinking about this pattern is to consider how we might model the color spectrum. There are something like 10 million visible colors, so modeling them as individual classes (e.g. Red
, LightMauve
, MacaroniAndCheese
, NotYellowButNotGreenEither
) would be rather impractical.
However, a color is a color, no matter what color it is; colors have the same kinds of properties as each other even if they don't have the same values for those properties. If we needed to create a lot of color instances, we could do so using the Prototype design pattern.
NOTE: This post is part of a series demonstrating software design patterns using C# and .NET. The patterns are taken from the book Design Patterns by the Gang of Four. Check out the other posts in this series!
The Rundown
- Type: Creational
- Useful? 3/5 (Sometimes)
- Good For: Creating lots of similar objects.
- Example Code: On GitHub
The Participants
- The Prototype declares an interface for cloning itself.
- The ConcretePrototype implements the cloning operation defined in the Prototype.
- The Client creates a new object by asking the Prototype to clone itself.
A Delicious Example
To demo this pattern, let's think about sandwiches (like we did for Factory Method and Abstract Factory).
In the picture above, there are many kinds of sandwiches. Just like the color example we discussed earlier, a sandwich is still a sandwich no matter what's between the two slices of bread. We can demonstrate how we can use the Prototype pattern to build lots of sandwiches.
First, we'll create an abstract class SandwichPrototype
(the Prototype participant) to represent a sandwich, and define a method Clone()
which an instance of this class can use to clone itself:
/// <summary>
/// The Prototype abstract class
/// </summary>
abstract class SandwichPrototype
{
public abstract SandwichPrototype Clone();
}
Now we need the ConcretePrototype participant class Sandwich
. This class can clone itself to create more Sandwich
instances. For our model, we'll say that a Sandwich consists of four parts:
- Meat
- Cheese
- Bread
- Veggies
Here's that class:
class Sandwich : SandwichPrototype
{
private string Bread;
private string Meat;
private string Cheese;
private string Veggies;
public Sandwich(string bread, string meat, string cheese, string veggies)
{
Bread = bread;
Meat = meat;
Cheese = cheese;
Veggies = veggies;
}
public override SandwichPrototype Clone()
{
string ingredientList = GetIngredientList();
Console.WriteLine("Cloning sandwich with ingredients: {0}", ingredientList.Remove(ingredientList.LastIndexOf(",")));
return MemberwiseClone() as SandwichPrototype;
}
private string GetIngredientList()
{
//...
}
}
class SandwichMenu
{
private Dictionary<string, SandwichPrototype> _sandwiches
= new Dictionary<string, SandwichPrototype>();
public SandwichPrototype this[string name]
{
get { return _sandwiches[name]; }
set { _sandwiches.Add(name, value); }
}
}
Now we need to create a bunch of sandwiches.
In our Main()
method (which does double-duty as our Client participant), we can do just that by instantiating the prototype and then cloning it, thereby populating our SandwichMenu
:
class Program
{
static void Main(string[] args)
{
SandwichMenu sandwichMenu = new SandwichMenu();
// Initialize with default sandwiches
sandwichMenu["BLT"]
= new Sandwich("Wheat", "Bacon", "", "Lettuce, Tomato");
sandwichMenu["PB&J"]
= new Sandwich("White", "", "", "Peanut Butter, Jelly");
sandwichMenu["Turkey"]
= new Sandwich("Rye", "Turkey", "Swiss", "Lettuce, Onion, Tomato");
// Deli manager adds custom sandwiches
sandwichMenu["LoadedBLT"]
= new Sandwich("Wheat", "Turkey, Bacon", "American", "Lettuce, Tomato, Onion, Olives");
sandwichMenu["ThreeMeatCombo"]
= new Sandwich("Rye", "Turkey, Ham, Salami", "Provolone", "Lettuce, Onion");
sandwichMenu["Vegetarian"]
= new Sandwich("Wheat", "", "", "Lettuce, Onion, Tomato, Olives, Spinach");
// Now we can clone these sandwiches
Sandwich sandwich1 = sandwichMenu["BLT"].Clone() as Sandwich;
Sandwich sandwich2
= sandwichMenu["ThreeMeatCombo"].Clone() as Sandwich;
Sandwich sandwich3
= sandwichMenu["Vegetarian"].Clone() as Sandwich;
// Wait for user
Console.ReadKey();
}
}
By the time we get to the line Console.ReadKey()
, how many total separate instances of Sandwich
do we have? Nine, six in the sandwichMenu
and three initialized as variables sandwich1, sandwich2, sandwich3
.
Will I Ever Use This Pattern?
Possibly. It's a good idea if you have the scenario described above. However, I'm not sure how common that scenario is in regular day-to-day coding; I haven't (consciously) implemented this pattern in several years.
The situation in which I see this pattern as being the most useful is when all of the following happens:
- You need to create a lot of instances of an object,
- AND those instances will be the same or similar as the prototypical instance.
- AND creating a new instance of this object would be markedly slower than cloning an existing instance.
If you have all of those conditions, the Prototype design pattern is for you!
Summary
The Prototype pattern initializes objects by cloning them from a prototypical instance of said object. It's especially useful when you need to create many instances of related items, each of which could be slightly (but not very) different from the other instances. The primary benefit of this pattern is reduced initialization costs; by cloning many instances from a prototypical instance, you theoretically improve performance.
Happy Coding!