Table of Contents
- 1.1 Introduction and objectives
- 1.2 Prerequisites
- 1.3 Topics covered
- 2.1 The Prototype Pattern explained
- 2.2 UML Diagram of the Prototype Pattern
- 2.3 Creation of the Order class
- 2.4 Object copy implementation
- 2.5 Shallow Copy vs Deep Copy
- 2.6 Adding a Prototype Manager
- 2.7 Use cases and implications
- 3.1 FoodOrder.cs
- 3.2 Program.cs
1. Course Overview
1.1 Introduction and objectives
Welcome to the course C# Design Patterns: Prototype, presented by Harrison Ferrone, software developer and educational author at Paradigm Shift Development.
This course is a starting point in the Prototype Design Pattern in C#. No prior experience with design patterns is required to get started.
Educational objectives: At the end of this course, you will be able to:
- Identify and analyze practical use cases for the Prototype Pattern
- Apply the code and skills learned in your own C# projects
1.2 Prerequisites
As this is an intermediate level course, you should be familiar with:
- The C# programming language
- The Visual Studio environment (Mac or Windows)
1.3 Topics covered
The main topics covered in this course are:
| Subject | Description |
|---|---|
| FoodOrder class | Define a Food Order Object Class |
| Copy/Clone interface | Add a copy and clone interface |
| Concrete cloning class | Create a concrete clone class |
| Shallow vs. Deep Copy | Differentiate between shallow and deep copy |
| Use cases | Use cases and practical applications |
2. Implementation of the Prototype Pattern
2.1 The Prototype Pattern explained
General context of Design Patterns
Design patterns are classified into three different categories, as defined by the Gang of Four text on elements of reusable object-oriented software:
┌─────────────────────────────────────────────────────┐
│ Catégories de Design Patterns │
├─────────────────┬──────────────────┬────────────────┤
│ CRÉATIONNELS │ STRUCTURELS │ COMPORTEMENTAUX│
│ (Creational) │ (Structural) │ (Behavioral) │
├─────────────────┼──────────────────┼────────────────┤
│ • Prototype │ • Adapter │ • Observer │
│ • Singleton │ • Bridge │ • Strategy │
│ • Factory │ • Composite │ • Command │
│ • Builder │ • Decorator │ • Iterator │
│ • Abstract Fct │ • Facade │ • Chain of Res.│
└─────────────────┴──────────────────┴────────────────┘
The Prototype Pattern — Category: Creational
The Prototype Pattern belongs to the Creative category. Its formal definition is:
It specifies the types of objects to create using a prototypical instance and creates new objects by copying this prototype.
At a high level: This pattern allows a client to request a clone of an existing object from the object itself.
On a smaller scale: This pattern can reduce your subclass hierarchy by allowing you to create configurable clones of existing classes instead of creating new subclasses.
When to use the Prototype Pattern?
You should implement the Prototype Pattern when:
- Object creation, composition and representation must be separated from a given system
- The classes to be instantiated are specified at execution (runtime)
- You want to avoid a factory hierarchy that reflects the class hierarchy of your products
- Class instances can only have a limited and finite combination of states
2.2 UML Diagram of the Prototype Pattern
The UML diagram of the Prototype Pattern is relatively simple. It has three main components:
┌─────────────────────────────────────────────────────────────┐
│ PROTOTYPE PATTERN UML │
└─────────────────────────────────────────────────────────────┘
┌──────────────────────┐
│ <<abstract>> │
│ Prototype │
├──────────────────────┤
│ + ShallowCopy() │◄─── Méthode blueprint pour
│ + DeepCopy() │ toutes les classes adoptantes
│ + Debug() │
└──────────┬───────────┘
│ hérite de
│
┌──────────▼───────────┐
│ ConcretePrototype │
│ (ex: FoodOrder) │
├──────────────────────┤
│ + ShallowCopy() │◄─── Chaque classe est responsable
│ + DeepCopy() │ de se cloner elle-même
│ + Debug() │
└──────────────────────┘
▲
│ utilise
│
┌──────────┴───────────┐
│ Client │
├──────────────────────┤
│ │◄─── Fait la demande de clonage
│ object.Clone() │ depuis l'objet existant
└──────────────────────┘
Description of components:
-
Prototype (abstract class): Defines the
Clonemethod as blueprint for all adopting classes. In our implementation, it is an abstract class with methodsShallowCopy(),DeepCopy()andDebug(). -
ConcretePrototype (concrete classes): As many “clonable” classes as necessary, each responsible for cloning itself and returning a copy on request.
-
Client: Performs the clone request from an existing object that implements the Prototype interface.
2.3 Creating the Order class
Example scenario
The scenario used for this course is that of a food ordering application. A handy feature of this app would be the copying of favorite food orders that customers have already saved.
Project implementation
- Open Visual Studio
- Create a new console application (Console Application) in C#
- Name the project:
Prototype Pattern - Add a new class:
FoodOrder.cs
Properties of the FoodOrder class
The FoodOrder class is simple but includes several essential properties:
public class FoodOrder : Prototype
{
public string customerName; // Nom du client
public bool isDelivery; // Livraison ou retrait en magasin
public string[] orderContents; // Contenu de la commande (tableau de chaînes)
public OrderInfo info; // Informations supplémentaires (référence à un objet)
}
Design note: Properties are declared
publicto simplify the example. Nothing prevents you from using private variables with public backing properties in this pattern.
Constructor of the FoodOrder class
public FoodOrder(string name, bool delivery, string[] contents, OrderInfo info)
{
this.customerName = name;
this.isDelivery = delivery;
this.orderContents = contents;
this.info = info;
}
Debug Method
A Debug() method is added to view the status of commands during cloning:
public override void Debug()
{
Console.WriteLine("------- Prototype Food Order -------");
Console.WriteLine("\nName: {0} \nDelivery: {1}", this.customerName, this.isDelivery);
Console.WriteLine("ID: {0}", this.info.id);
Console.WriteLine("Order Contents: " + string.Join(",", orderContents) + "\n");
}
OrderInfo class — Reference type
To demonstrate the difference between Shallow Copy and Deep Copy, an OrderInfo class is introduced. It represents a reference type:
public class OrderInfo
{
public int id;
public OrderInfo(int id)
{
this.id = id;
}
}
Initial usage example
FoodOrder savedOrder = new FoodOrder(
"Harrison",
true,
new string[] { "Pizza", "Coke" },
new OrderInfo(2345)
);
savedOrder.Debug();
Expected console output:
------- Prototype Food Order -------
Name: Harrison
Delivery: True
ID: 2345
Order Contents: Pizza,Coke
2.4 Implementing object copy
The Prototype abstract class
To enable cloning, an abstract class Prototype is defined. All cloneable classes must inherit it:
public abstract class Prototype
{
public abstract Prototype ShallowCopy();
public abstract Prototype DeepCopy();
public abstract void Debug();
}
Why include Debug() in the abstract class?
Although Debug() is already implemented in FoodOrder, keeping it in the abstract class forces all prototypable classes to include it. This ensures consistency and improves readability for the entire development team.
FoodOrder inherits from Prototype:
public class FoodOrder : Prototype
{
// ...
}
2.5 Shallow Copy vs Deep Copy
Shallow Copy
What is MemberwiseClone?
The MemberwiseClone method returns a shallow copy of the current object. It is available on all objects in C# (inherited from System.Object).
Behavior of Shallow Copy:
- All non-static value types are copied to the clone
- For reference types, only the memory address is copied (not the object itself)
┌─────────────────────────────────────────────────────────────┐
│ SHALLOW COPY │
├─────────────────────────────────────────────────────────────┤
│ │
│ Original Clone (Shallow) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ customerName:"A" │ ──────►│ customerName:"A" │ (copié) │
│ │ isDelivery: true │ ──────►│ isDelivery: true │ (copié) │
│ │ orderContents[] │ ──────►│ orderContents[] │ (copié) │
│ │ info ──────────┐ │ │ info ──────────┐ │ │
│ └────────────────│─┘ └────────────────│─┘ │
│ └──────────────────────────►│ │
│ même référence ! │ │
│ ┌─────────────────────▼──┐ │
│ │ OrderInfo { id: 2345 }│ │
│ └────────────────────────┘ │
│ │
│ ⚠️ Modifier info.id sur l'original AFFECTE aussi le clone ! │
└─────────────────────────────────────────────────────────────┘
Implementing ShallowCopy
public override Prototype ShallowCopy()
{
return (Prototype)this.MemberwiseClone();
}
Shallow Copy issue demonstration
// Ordre original
FoodOrder savedOrder = new FoodOrder(
"Harrison",
true,
new string[] { "Pizza", "Coke" },
new OrderInfo(2345)
);
// Clone superficiel
FoodOrder clonedOrder = (FoodOrder)savedOrder.ShallowCopy();
// Modifications sur l'original
savedOrder.customerName = "Jeff"; // ✅ N'affecte PAS le clone
savedOrder.info.id = 5555; // ❌ AFFECTE aussi le clone !
savedOrder.Debug(); // Name: Jeff, ID: 5555
clonedOrder.Debug(); // Name: Harrison, ID: 5555 ← problème !
Console output:
------- Prototype Food Order -------
Name: Jeff
Delivery: True
ID: 5555
Order Contents: Pizza,Coke
------- Prototype Food Order -------
Name: Harrison ← ✅ La chaîne n'est pas affectée
Delivery: True
ID: 5555 ← ❌ L'ID a été modifié dans le clone aussi !
Order Contents: Pizza,Coke
Important:
stringare technically reference types in C#, but in this case they transfer correctly during a shallow copy (they are treated as immutable values).
Deep Copy
Purpose of Deep Copy
Deep Copy solves the Shallow Copy problem by creating a new instance for each reference type, so that the original and the clone are completely independent.
┌─────────────────────────────────────────────────────────────┐
│ DEEP COPY │
├─────────────────────────────────────────────────────────────┤
│ │
│ Original Clone (Deep) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ customerName:"A" │ ──────►│ customerName:"A" │ (copié) │
│ │ isDelivery: true │ ──────►│ isDelivery: true │ (copié) │
│ │ orderContents[] │ ──────►│ orderContents[] │ (copié) │
│ │ info ──────────┐ │ │ info ──────────┐ │ │
│ └────────────────│─┘ └────────────────│─┘ │
│ │ │ │
│ ┌────────────────▼──┐ ┌───────────────────▼──┐ │
│ │ OrderInfo{id:2345}│ │ OrderInfo{id:2345} │ │
│ └───────────────────┘ └──────────────────────┘ │
│ INSTANCE 1 INSTANCE 2 │
│ │
│ ✅ Modifier info.id sur l'original n'affecte PAS le clone ! │
└─────────────────────────────────────────────────────────────┘
Approaches to implementing Deep Copy
There are several ways to implement Deep Copy. The simplest is to add a DeepCopy() method to the Prototype class and manually re-configure the reference types after creating a shallow clone.
Implementing DeepCopy
public override Prototype DeepCopy()
{
// Étape 1 : Créer un clone superficiel pour les types valeur
FoodOrder clonedOrder = (FoodOrder)this.MemberwiseClone();
// Étape 2 : Créer une nouvelle instance pour les types référence
clonedOrder.info = new OrderInfo(this.info.id);
return clonedOrder;
}
Why not call
ShallowCopy()fromDeepCopy()? In most cases you will not have both methods in your program. You will choose the one that meets your needs. It is therefore better to keep them completely separate.
Deep Copy Demonstration
// Ordre original
FoodOrder savedOrder = new FoodOrder(
"Harrison",
true,
new string[] { "Pizza", "Coke" },
new OrderInfo(2345)
);
// Clone profond
FoodOrder clonedOrder = (FoodOrder)savedOrder.DeepCopy();
// Modifications sur l'original
savedOrder.customerName = "Jeff"; // ✅ N'affecte PAS le clone
savedOrder.info.id = 5555; // ✅ N'affecte PAS le clone
savedOrder.Debug(); // Name: Jeff, ID: 5555
clonedOrder.Debug(); // Name: Harrison, ID: 2345 ← ✅ intact !
Console output:
------- Prototype Food Order -------
Name: Jeff
Delivery: True
ID: 5555
Order Contents: Pizza,Coke
------- Prototype Food Order -------
Name: Harrison ← ✅ Non affecté
Delivery: True
ID: 2345 ← ✅ Non affecté non plus !
Order Contents: Pizza,Coke
Shallow Copy vs Deep Copy Comparison
| Characteristic | Shallow Copy | Deep Copy |
|---|---|---|
| Value types | ✅ Copied independently | ✅ Copied independently |
| Reference types | ❌ Same memory address | ✅ New instance created |
| Modification of the original | May affect the clone | Does not affect the clone |
| C# method | MemberwiseClone() | MemberwiseClone() + manual recreation of referenced objects |
| Use cases | Objects without reference types | Objects with reference types |
| Complexity | Simple | Requires more code |
2.6 Adding a Prototype Manager
Why a Prototype Manager?
The Prototype Manager is an important variation that you will often encounter with the Prototype Pattern. Its role is:
- Keep track of a prototype object dictionary by key-value pair
- Facilitate reference by customer code
- Eliminate the need to create objects on the fly
Implementation of the PrototypeManager class
public class PrototypeManager
{
// Dictionnaire privé pour stocker les prototypes
private Dictionary<string, Prototype> _prototypeLibrary = new Dictionary<string, Prototype>();
// Indexeur pour accéder aux prototypes par clé string
public Prototype this[string key]
{
get { return _prototypeLibrary[key]; }
set { _prototypeLibrary.Add(key, value); }
}
}
Key implementation points:
- Private dictionary
_prototypeLibrary: Stores prototypes with astringkey and aPrototypetype value - Indexer: Allows clean syntactic access and modification via
manager["key"] - No explicit constructor: For this example, the objects are added manually via the indexer. You could absolutely declare a constructor that initializes the
PrototypeManagerwith the objects you want to store.
Using the PrototypeManager
// Création du manager
PrototypeManager manager = new PrototypeManager();
// Ajout d'une commande indexée par date
manager["2/3/2020"] = new FoodOrder(
"Steve",
false, // Pas de livraison (retrait en magasin)
new string[] { "Chicken Parm", "Root Beer" },
new OrderInfo(8912)
);
// Récupération et clonage profond depuis le manager
Console.WriteLine("Manager clone:");
FoodOrder managerOrder = (FoodOrder)manager["2/3/2020"].DeepCopy();
managerOrder.Debug();
Expected console output:
Manager clone:
------- Prototype Food Order -------
Name: Steve
Delivery: False
ID: 8912
Order Contents: Chicken Parm,Root Beer
Benefits of Prototype Manager
-
Scalability: This approach scales very well to a large number of prototype objects, especially if you pass commands to the
PrototypeManagerduring initialization. -
Compatibility: The Object Pool pattern works extremely well with the Prototype, especially if it has a manager class.
-
Flexibility: Although this example only uses a single prototype object, it doesn’t have to be this way. The whole point of the abstract
Prototypeclass is that it can be applied to an indefinite number of objects that we might want to copy. Everything we’ve done will work just fine with a variety of different prototype objects, provided they all inherit from thePrototypeclass and implement their own cloning and debugging methods.
Sequence diagram — Using the PrototypeManager
Client PrototypeManager FoodOrder (Prototype)
│ │ │
│ manager["key"] = │ │
│ new FoodOrder(...)│ │
│───────────────────►│ │
│ │── _prototypeLibrary.Add()│
│ │ │
│ manager["key"] │ │
│ .DeepCopy() │ │
│───────────────────►│ │
│ │── _prototypeLibrary[key] │
│ │────────────────────────►│
│ │ │── MemberwiseClone()
│ │ │── new OrderInfo(id)
│ │◄────── clone ───────────│
│◄── clone ──────────│ │
│ │ │
│ clone.Debug() │ │
│───────────────────────────────────────────► │
2.7 Use cases and implications
Common application scenarios
The Prototype Pattern is particularly suitable in the following situations:
-
Classes instantiated at runtime: When the classes to be instantiated are dynamically specified during program execution, not during compilation.
-
Avoiding a factory hierarchy: When you want to avoid a factory class hierarchy that would mirror the class hierarchy of your products.
-
Limited state combinations: When class instances can only have a limited and finite number of state combinations.
Golden rule: Like all design patterns, implementing each object in your project as a prototype is not the right approach and can even be excessive. The main thing is to find the places where it really makes sense and use it correctly.
Comparison with other creational patterns
If you are familiar with other creative patterns, notably Builder and Abstract Factory, you will probably have noticed that they share several implications with the Prototype:
| Involvement | Prototype | Builder | Abstract Factory |
|---|---|---|---|
| Hidden client concrete classes | ✅ | ✅ | ✅ |
| Products that can be added/removed at runtime | ✅ | ❌ | ❌ |
| Dynamic Object Composition | ✅ | ✅ | Partial |
| Reduced class hierarchy | ✅ | ✅ | ❌ |
| Usable as a component in a larger design | ✅ | ✅ | ✅ |
Four key implications of the Prototype Pattern
-
Concrete classes hidden from the client: The client code does not need to know the concrete classes; it only works with the abstract interface
Prototype. -
Products added/removed at runtime: As already noted, products can be added or removed dynamically by the client.
-
More dynamic system via object composition: Using cloned objects with varied configurations makes your system more dynamic through this type of object composition, and your class hierarchy will be cleaner and more efficient.
-
Increased Modularity: Prototype objects can be used as components in a larger object creation design, which can also increase the modularity of your code.
3. Full source code
3.1 FoodOrder.cs
This file contains all the classes of the Prototype Pattern: the abstract class Prototype, the class OrderInfo, the concrete class FoodOrder and the PrototypeManager.
using System;
using System.Collections.Generic;
using System.Text;
namespace Prototype_Pattern
{
// Classe abstraite définissant le contrat de clonage
public abstract class Prototype
{
public abstract Prototype ShallowCopy();
public abstract Prototype DeepCopy();
public abstract void Debug();
}
// Type référence pour démontrer la différence Shallow/Deep Copy
public class OrderInfo
{
public int id;
public OrderInfo(int id)
{
this.id = id;
}
}
// Classe concrète prototypable — hérite de Prototype
public class FoodOrder : Prototype
{
public string customerName;
public bool isDelivery;
public string[] orderContents;
public OrderInfo info; // ← Type référence !
public FoodOrder(string name, bool delivery, string[] contents, OrderInfo info)
{
this.customerName = name;
this.isDelivery = delivery;
this.orderContents = contents;
this.info = info;
}
// Copie superficielle : les types référence partagent la même adresse
public override Prototype ShallowCopy()
{
return (Prototype)this.MemberwiseClone();
}
// Copie profonde : tous les types référence sont recréés
public override Prototype DeepCopy()
{
FoodOrder clonedOrder = (FoodOrder)this.MemberwiseClone();
clonedOrder.info = new OrderInfo(this.info.id); // ← Nouvelle instance !
return clonedOrder;
}
// Méthode de débogage pour visualiser l'état de la commande
public override void Debug()
{
Console.WriteLine("------- Prototype Food Order -------");
Console.WriteLine("\nName: {0} \nDelivery: {1}", this.customerName, this.isDelivery);
Console.WriteLine("ID: {0}", this.info.id);
Console.WriteLine("Order Contents: " + string.Join(",", orderContents) + "\n");
}
}
// Variante manager : stocke un dictionnaire de prototypes accessibles par clé
public class PrototypeManager
{
private Dictionary<string, Prototype> _prototypeLibrary = new Dictionary<string, Prototype>();
// Indexeur pour accès et modification syntaxiques
public Prototype this[string key]
{
get { return _prototypeLibrary[key]; }
set { _prototypeLibrary.Add(key, value); }
}
}
}
3.2 Program.cs
The main program demonstrates the use of the PrototypeManager with DeepCopy().
using System;
namespace Prototype_Pattern
{
class Program
{
static void Main(string[] args)
{
/*
* Exemple de Shallow Copy vs Deep Copy (code commenté) :
*
* Console.WriteLine("Original order:");
* FoodOrder savedOrder = new FoodOrder(
* "Harrison",
* true,
* new string[] { "Pizza", "Coke"},
* new OrderInfo(2345)
* );
* savedOrder.Debug();
*
* Console.WriteLine("Cloned order:");
* FoodOrder clonedOrder = (FoodOrder)savedOrder.DeepCopy();
* clonedOrder.Debug();
*
* Console.WriteLine("Order changes:");
* savedOrder.customerName = "Jeff";
* savedOrder.info.id = 5555;
* savedOrder.Debug();
* clonedOrder.Debug();
*/
// === Utilisation du PrototypeManager ===
PrototypeManager manager = new PrototypeManager();
// Ajout d'une commande indexée par date
manager["2/3/2020"] = new FoodOrder(
"Steve",
false,
new string[] { "Chicken Parm", "Root Beer" },
new OrderInfo(8912)
);
// Récupération et clonage depuis le manager
Console.WriteLine("Manager clone:");
FoodOrder managerOrder = (FoodOrder)manager["2/3/2020"].DeepCopy();
managerOrder.Debug();
}
}
}
Program console output:
Manager clone:
------- Prototype Food Order -------
Name: Steve
Delivery: False
ID: 8912
Order Contents: Chicken Parm,Root Beer
4. Summary and key points
What we learned
-
Create a Prototype abstract class which imposes a cloning blueprint on the adopting classes.
-
Distinguish and implement Shallow Copy and Deep Copy:
- Shallow Copy uses
MemberwiseClone()and copies addresses of reference types - Deep Copy uses
MemberwiseClone()and manually recreates instances of reference types
-
Configure a Prototype Manager to store and retrieve prototype objects more efficiently via a key-indexed dictionary.
-
Identify and work with the Prototype Pattern in real-world scenarios, including control applications, object configuration systems, and architectures requiring dynamic composition.
Overall architecture of the pattern
┌──────────────────────────────────────────────────────────────────┐
│ PROTOTYPE PATTERN — Vue globale │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Namespace: Prototype_Pattern │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ <<abstract>> │ │ PrototypeManager │ │ │
│ │ │ Prototype │ │ ───────────────────── │ │ │
│ │ │ ───────────── │ │ -_prototypeLibrary: │ │ │
│ │ │ +ShallowCopy() │ │ Dictionary<str,Prot> │ │ │
│ │ │ +DeepCopy() │ │ ───────────────────── │ │ │
│ │ │ +Debug() │ │ +this[string key] │ │ │
│ │ └────────┬─────────┘ └──────────────────────────┘ │ │
│ │ │ hérite │ contient │ │
│ │ ┌────────▼────────────┐ │ │ │
│ │ │ FoodOrder │◄────────────┘ │ │
│ │ │ ────────────────── │ │ │
│ │ │ +customerName:string│ ┌─────────────────────────┐ │ │
│ │ │ +isDelivery:bool │ │ OrderInfo │ │ │
│ │ │ +orderContents:str[]│ │ ──────────────────────── │ │ │
│ │ │ +info:OrderInfo ────┼───►│ +id:int │ │ │
│ │ │ ────────────────── │ │ ──────────────────────── │ │ │
│ │ │ +ShallowCopy() │ │ +OrderInfo(id:int) │ │ │
│ │ │ +DeepCopy() │ └─────────────────────────┘ │ │
│ │ │ +Debug() │ │ │
│ │ └─────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Implementation Checklist
Before implementing the Prototype Pattern, check the following:
- Object creation, composition and representation must be separated from the system
- Classes have configurations that vary slightly from instance to instance
- Creating objects is costly (in time or resources)
- You need independence between original and clone
- You have identified whether you need Shallow Copy or Deep Copy
Best practices
-
Choice between Shallow and Deep Copy: Analyze your objects. If all fields are value types (int, bool, struct), a Shallow Copy is sufficient. If reference types are present, use Deep Copy.
-
Prototype Manager: Use a
PrototypeManagerwhenever you have several prototypes to manage or when objects must be accessible from several places in the code. -
Pattern Object Pool: This pattern works extremely well with the Prototype, especially if the latter has a manager class.
-
Don’t overdo it: Implementing every object in your project as a prototype is excessive. Identify cases where creating new objects through cloning provides real value.
-
Debug method: Always include a
Debug()method in your abstractPrototypeclass to force all concrete classes to offer diagnostic capability.
Search Terms
c-sharp · design · patterns · prototype · testing · architecture · c# · .net · development · copy · pattern · class · deep · shallow · manager · prototypemanager · comparison · creational · demonstration · diagram · foodorder · implications