Skip to content Skip to footer

Creational Design Patterns: Prototype

Clone Instead of Reconstruct

What if creating a new object is so expensive that copying an existing one is faster? What if you need to create objects without knowing their exact class? The Prototype pattern lets you clone existing objects, bypassing costly construction and tight coupling to concrete classes.

The Problem: Expensive Object Creation

You’re building a document editor where documents have complex configurations:

public class Document
{
    public string Title { get; set; }
    public List<Section> Sections { get; set; } = new();
    public DocumentSettings Settings { get; set; }
    public byte[] Template { get; set; }
    
    public Document()
    {
        // Expensive operations
        LoadTemplateFromDisk(); // 500ms
        InitializeComplexSections(); // 300ms
        ApplyDefaultFormatting(); // 200ms
        LoadFonts(); // 400ms
        // Total: 1.4 seconds per document!
    }
}

// Creating 100 similar documents
for (int i = 0; i < 100; i++)
{
    var doc = new Document(); // 1.4s each = 140 seconds total!
    doc.Title = $"Report {i}";
    // Same template, same settings, just different title
}

Problems:

  • Creating each object is computationally expensive
  • Most properties are identical across instances
  • Constructor doesn’t expose what needs to be different
  • Cannot create object copies without knowing concrete types
  • Tight coupling to specific classes

The Solution: Prototype Pattern

The Prototype pattern creates new objects by cloning existing ones, allowing you to bypass expensive construction and create copies without coupling to concrete classes.

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

Pseudocode Structure

IPrototype (interface)
└── Clone() : IPrototype

ConcretePrototype1 : IPrototype
└── Clone() : returns deep copy of self

ConcretePrototype2 : IPrototype
└── Clone() : returns deep copy of self

Client
└── Uses IPrototype.Clone() to create new instances

Real-World Example: Game Character System

Step 1: Define the Prototype Interface

public interface IGameCharacter
{
    IGameCharacter Clone();
    void Display();
    string GetCharacterInfo();
}

Step 2: Implement Complex Character Classes

public class Character : IGameCharacter
{
    public string Name { get; set; } = string.Empty;
    public int Level { get; set; }
    public int Health { get; set; }
    public int Mana { get; set; }
    public List<string> Abilities { get; set; } = new();
    public Dictionary<string, int> Stats { get; set; } = new();
    public Equipment Equipment { get; set; } = new();
    public Inventory Inventory { get; set; } = new();

    public Character()
    {
        // Simulate expensive initialization
        Console.WriteLine("🔨 Expensive character initialization...");
        System.Threading.Thread.Sleep(100); // Simulate database load
        LoadDefaultStats();
        InitializeEquipment();
        LoadAbilityTree();
    }

    private void LoadDefaultStats()
    {
        Stats["Strength"] = 10;
        Stats["Agility"] = 10;
        Stats["Intelligence"] = 10;
    }

    private void InitializeEquipment()
    {
        Equipment = new Equipment
        {
            Weapon = "Wooden Sword",
            Armor = "Cloth Armor"
        };
    }

    private void LoadAbilityTree()
    {
        Abilities.AddRange(new[] { "Basic Attack", "Defend" });
    }

    public IGameCharacter Clone()
    {
        // Shallow copy with MemberwiseClone, then deep copy collections
        var clone = (Character)MemberwiseClone();
        
        // Deep copy collections to avoid shared references
        clone.Abilities = new List<string>(Abilities);
        clone.Stats = new Dictionary<string, int>(Stats);
        clone.Equipment = new Equipment
        {
            Weapon = Equipment.Weapon,
            Armor = Equipment.Armor
        };
        clone.Inventory = new Inventory
        {
            Items = new List<string>(Inventory.Items)
        };

        Console.WriteLine($"✨ Cloned character: {Name} -> {clone.Name}");
        return clone;
    }

    public void Display()
    {
        Console.WriteLine($"\n👤 {Name} (Level {Level})");
        Console.WriteLine($"   ❤️  Health: {Health} | 💙 Mana: {Mana}");
        Console.WriteLine($"   ⚔️  Weapon: {Equipment.Weapon}");
        Console.WriteLine($"   🛡️  Armor: {Equipment.Armor}");
        Console.WriteLine($"   ✨ Abilities: {string.Join(", ", Abilities)}");
    }

    public string GetCharacterInfo() => $"{Name} - Level {Level}";
}

public class Equipment
{
    public string Weapon { get; set; } = string.Empty;
    public string Armor { get; set; } = string.Empty;
}

public class Inventory
{
    public List<string> Items { get; set; } = new();
}

Step 3: Create Prototype Registry

public class CharacterRegistry
{
    private readonly Dictionary<string, IGameCharacter> _prototypes = new();

    public void RegisterPrototype(string key, IGameCharacter prototype)
    {
        _prototypes[key] = prototype;
        Console.WriteLine($"📋 Registered prototype: {key}");
    }

    public IGameCharacter? GetPrototype(string key)
    {
        return _prototypes.TryGetValue(key, out var prototype) 
            ? prototype.Clone() 
            : null;
    }

    public void ListPrototypes()
    {
        Console.WriteLine("\n📚 Available Prototypes:");
        foreach (var key in _prototypes.Keys)
        {
            Console.WriteLine($"   - {key}: {_prototypes[key].GetCharacterInfo()}");
        }
    }
}

Step 4: Use the Prototype

public class Program
{
    public static void Main(string[] args)
    {
        var registry = new CharacterRegistry();

        // Create expensive prototype warriors (only once!)
        Console.WriteLine("Creating prototype characters...\n");
        
        var warriorPrototype = new Character
        {
            Name = "Warrior Template",
            Level = 1,
            Health = 150,
            Mana = 50
        };
        warriorPrototype.Stats["Strength"] = 20;
        warriorPrototype.Stats["Agility"] = 12;
        warriorPrototype.Abilities.Add("Sword Slash");
        warriorPrototype.Equipment.Weapon = "Iron Sword";
        warriorPrototype.Equipment.Armor = "Chain Mail";

        var magePrototype = new Character
        {
            Name = "Mage Template",
            Level = 1,
            Health = 80,
            Mana = 200
        };
        magePrototype.Stats["Intelligence"] = 25;
        magePrototype.Stats["Agility"] = 8;
        magePrototype.Abilities.AddRange(new[] { "Fireball", "Ice Blast" });
        magePrototype.Equipment.Weapon = "Staff";
        magePrototype.Equipment.Armor = "Robe";

        // Register prototypes
        registry.RegisterPrototype("Warrior", warriorPrototype);
        registry.RegisterPrototype("Mage", magePrototype);
        
        registry.ListPrototypes();

        // Clone characters instantly - no expensive initialization!
        Console.WriteLine("\n\nCreating game characters via cloning...\n");
        
        var player1 = (Character)registry.GetPrototype("Warrior")!;
        player1.Name = "Aragorn";
        player1.Level = 5;

        var player2 = (Character)registry.GetPrototype("Warrior")!;
        player2.Name = "Boromir";
        player2.Level = 4;

        var player3 = (Character)registry.GetPrototype("Mage")!;
        player3.Name = "Gandalf";
        player3.Level = 10;
        player3.Abilities.Add("Lightning Strike");

        // Display all characters
        player1.Display();
        player2.Display();
        player3.Display();

        // Verify independence (changing one doesn't affect others)
        Console.WriteLine("\n\n🔍 Verifying clone independence...");
        player1.Abilities.Add("Berserk");
        player1.Display();
        player2.Display(); // Boromir should NOT have Berserk
    }
}

Advanced Example: Configuration Templates

public interface IConfiguration
{
    IConfiguration Clone();
    void Display();
}

public class DatabaseConfiguration : IConfiguration
{
    public string ConnectionString { get; set; } = string.Empty;
    public int MaxConnections { get; set; }
    public TimeSpan Timeout { get; set; }
    public Dictionary<string, string> Settings { get; set; } = new();
    public List<string> BackupServers { get; set; } = new();

    public IConfiguration Clone()
    {
        var clone = (DatabaseConfiguration)MemberwiseClone();
        clone.Settings = new Dictionary<string, string>(Settings);
        clone.BackupServers = new List<string>(BackupServers);
        return clone;
    }

    public void Display()
    {
        Console.WriteLine($"DB Config: {ConnectionString}");
        Console.WriteLine($"Max Connections: {MaxConnections}, Timeout: {Timeout}");
        Console.WriteLine($"Backups: {string.Join(", ", BackupServers)}");
    }
}

// Usage for multi-tenant systems
var baseConfig = new DatabaseConfiguration
{
    MaxConnections = 100,
    Timeout = TimeSpan.FromSeconds(30),
    Settings = { ["Pooling"] = "true", ["Encrypt"] = "true" }
};

// Clone for each tenant with minimal changes
var tenant1Config = (DatabaseConfiguration)baseConfig.Clone();
tenant1Config.ConnectionString = "Server=db1.example.com;Database=Tenant1";
tenant1Config.BackupServers.Add("backup1.example.com");

var tenant2Config = (DatabaseConfiguration)baseConfig.Clone();
tenant2Config.ConnectionString = "Server=db2.example.com;Database=Tenant2";
tenant2Config.BackupServers.Add("backup2.example.com");

Deep vs Shallow Copying

public class Document : IPrototype
{
    public string Title { get; set; } = string.Empty;
    public List<string> Tags { get; set; } = new();
    public Author Author { get; set; } = new();

    // Shallow Clone - DANGEROUS! Shares references
    public IPrototype ShallowClone()
    {
        return (Document)MemberwiseClone();
        // Tags and Author are shared between original and clone!
    }

    // Deep Clone - SAFE! Independent copies
    public IPrototype Clone()
    {
        var clone = (Document)MemberwiseClone();
        clone.Tags = new List<string>(Tags);
        clone.Author = new Author 
        { 
            Name = Author.Name, 
            Email = Author.Email 
        };
        return clone;
    }
}

public class Author
{
    public string Name { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

Enterprise Applications

1. Multi-Tenant Configuration

Clone base configurations for each tenant with tenant-specific overrides.

2. Testing Frameworks

Create test data objects from prototypes with specific variations.

3. Game Development

Spawn enemies, items, and NPCs from predefined templates.

4. Document Processing

Clone document templates and customize content without rebuilding structure.

5. Cache Warming

Pre-create expensive objects and clone them on demand.

Benefits

  • Performance: Avoid expensive initialization by cloning
  • Decoupling: Create objects without knowing concrete classes
  • Flexibility: Customize clones without affecting prototypes
  • Reduced Subclassing: No need for parallel factory hierarchies

When to Use

Use Prototype when:

  • Object creation is expensive (database loads, file I/O, complex calculations)
  • You need to create objects dynamically at runtime
  • Avoiding tight coupling to concrete classes is important
  • Most object properties are identical across instances
  • The system needs to be independent of how products are created

When NOT to Use

Avoid this pattern when:

  • Object creation is cheap and simple
  • Objects have few variations
  • Deep copying is complex and error-prone
  • Objects contain circular references

Common Pitfalls

Shallow Copy Trap:

// WRONG - Shares list reference!
public Character Clone()
{
    return (Character)MemberwiseClone(); 
}

// RIGHT - Deep copies collections
public Character Clone()
{
    var clone = (Character)MemberwiseClone();
    clone.Abilities = new List<string>(Abilities);
    return clone;
}

Key Takeaway: The Prototype pattern optimizes object creation by cloning existing instances instead of constructing new ones from scratch, especially valuable when initialization is expensive or when you need to create objects without knowing their concrete types at compile time.

Leave a Comment