patron-prototype

Prototype Pattern: Shallow Copy vs Deep Copy

  • 4 min

The Prototype is a pattern that creates new objects by cloning an existing instance. It prevents the client from depending on a concrete class and can save work when preparing the initial object is costly.

Imagine you’re developing a strategy video game and need to generate an army of 1000 orcs. If each construction repeats expensive calculations, cloning a pre-configured setup can be cheaper than rebuilding it from scratch. Heavy resources, like textures, are usually shared via a cache.

We can create a base orc, configure it once, and ask the system to copy it. The real cost will depend on which data gets cloned and which is shared.

The Concept

The pattern specifies that objects we want to copy must implement an interface (usually named IPrototype or ICloneable) that declares a Clone() method.

This way, the client code doesn’t need to know which concrete class it is copying. It only knows that calling Clone() gives back an exact replica.

The Big Dilemma: Shallow Copy vs Deep Copy

This is where the pattern gets tricky and where most bugs appear. When we copy an object, what are we actually copying?

Suppose our object contains a reference to another object (e.g., a User that has an Address).

Shallow Copy

We copy the values of the object’s fields.

  • If a field is a value type (int, float), the value is copied.
  • If a field is a reference (a class), the reference (the pointer) is copied, not the object it points to.

Result: The original object and the clone share the same Address. If you modify the street in the clone, the original changes too! This is usually a disaster.

Deep Copy

We copy the object and, recursively, we also copy all the objects it references.

  • A new instance of Address is created for the clone.

Result: The objects are completely independent. This is what we usually want, but it’s more expensive to process.

Implementation in C#

In .NET we have the ICloneable interface, but it has a bad reputation because it doesn’t specify whether the copy should be shallow or deep. We will make it explicit.

Example: The Problem with Shallow Copy

We’ll use C#‘s MemberwiseClone() method, which performs an automatic shallow copy.

public class InfoID
{
    public int IdNumber;

    public InfoID(int id) => IdNumber = id;
}

public class Persona
{
    public int Edad;
    public string Nombre;
    public InfoID IdInfo; // Reference to another object

    public Persona ShallowCopy()
    {
        // MemberwiseClone creates a shallow copy:
        // Copies 'Edad' and 'Nombre' correctly.
        // But it copies the REFERENCE of 'IdInfo'.
        return (Persona)this.MemberwiseClone();
    }
}
Copied!

If we use this:

Persona p1 = new Persona();
p1.IdInfo = new InfoID(666);

Persona p2 = p1.ShallowCopy();
p2.IdInfo.IdNumber = 999; // Modify the clone

Console.WriteLine(p1.IdInfo.IdNumber); // Prints 999! We broke the original.
Copied!

Example: Implementing Deep Copy

To do it correctly, we must ensure we also clone the dependencies.

public class Persona
{
    public int Edad;
    public string Nombre;
    public InfoID IdInfo;

    // Method for deep copy
    public Persona DeepCopy()
    {
        // 1. Perform the shallow copy first
        Persona clon = (Persona)this.MemberwiseClone();
        
        // 2. MANUALLY create new instances for the references
        clon.IdInfo = new InfoID(this.IdInfo.IdNumber);
        
        // If InfoID had further objects inside, we would need to clone them too.
        // clon.Nombre is a string, and strings in C# are immutable,
        // so there's no need to clone them explicitly.
        
        return clon;
    }
}
Copied!

A common technique in C# for quick Deep Copy (though not the most efficient) is to serialize the object to JSON and deserialize it right away. When reconstructing it, all new instances are created.

Prototype Registry

Sometimes we don’t clone an object we just created; instead, we have a “catalog” of predefined objects.

Imagine a level editor. You have a palette with enemy types: “Weak Orc”, “Strong Orc”, “Orc Boss”. You don’t create a new one each time; you ask the registry: “Give me a copy of the Strong Orc”.

public class RegistroMonstruos
{
    private Dictionary<string, Monstruo> _prototipos = new Dictionary<string, Monstruo>();

    public RegistroMonstruos()
    {
        // Initialize the base prototypes
        _prototipos.Add("Fantasma", new Monstruo { Tipo = "Fantasma", Ataque = 10 });
        _prototipos.Add("Demonio", new Monstruo { Tipo = "Demonio", Ataque = 50 });
    }

    public Monstruo CrearMonstruo(string tipo)
    {
        // Return a CLONE, not the original
        return _prototipos[tipo].DeepCopy();
    }
}
Copied!

When to Use Prototype

  1. Optimization: When the cost of initializing an object is high (Database, Network, complex calculations).
  2. Configuration: When you have many similar instances and only a couple of parameters differ. It’s easier to clone and modify the differences than to build from scratch.
  3. Independence: When you want to decouple your system from the class hierarchy of the objects it creates (similar to Factory, but by cloning).