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.
