Excerpt: Learn how the Prototype Pattern simplifies object creation by cloning existing instances. Explore real-world use cases, practical examples in C# and Python, and discover how to implement deep vs. shallow copies effectively.
What is the Prototype Pattern?
The Prototype Pattern is a creational design pattern that allows you to create new objects by copying an existing object (the prototype). Rather than building a new object from the ground up (which can be resource-intensive or complex), the Prototype Pattern lets you start with a pre-existing object—a prototype—and simply clone it to produce new instances. This is especially useful when the original object is already properly configured or has a specific internal state you want to preserve or slightly modify.
The page builds on this by showing how this pattern works in programming languages like C# and Python, using practical examples such as cloning an Employee object. It also emphasizes why this approach is valuable: it saves setup time, supports dynamic behavior (e.g. runtime customization), and keeps your code DRY (Don’t Repeat Yourself). , rather than instantiating new ones from scratch.
Shallow vs. Deep Copy
Shallow Copy: This duplicates the object’s outer structure but not its inner layers. So, if the object contains nested or referenced objects (like lists or other objects), those aren’t truly copied—they're just shared between the original and the copy. Any changes made to those nested parts will show up in both.
Deep Copy: This goes further. It duplicates not just the top-level object, but everything it references, recursively. That means the original and the copy are completely independent, even in their nested components.
Code Example
Here’s how you can implement shallow and deep copies in C# and Python:
// Shallow Copy
public class Address {
public string City;
}
public class Person {
public string Name;
public Address Address;
public Person ShallowCopy() {
return (Person)this.MemberwiseClone();
}
}
// Deep Copy
public class Person {
public string Name;
public Address Address;
public Person DeepCopy() {
return new Person {
Name = this.Name,
Address = new Address { City = this.Address.City }
};
}
}
public class Address {
public string City;
}
// Usage
var original = new Person { Name = "Alice", Address = new Address { City = "Paris" } };
var shallow = original.ShallowCopy();
shallow.Address.City = "London"; // Affects original
var deep = original.DeepCopy();
deep.Address.City = "Tokyo"; // Does not affect original
import copy
class Address:
def __init__(self, city):
self.city = city
class Person:
def __init__(self, name, address):
self.name = name
self.address = address
# Shallow Copy
original = Person(\"Alice\", Address(\"Paris\"))
shallow = copy.copy(original)
shallow.address.city = \"London\" # Affects original
# Deep Copy
deep = copy.deepcopy(original)
deep.address.city = \"Tokyo\" # Does not affect original
Use Cases
- Game development: Clone characters or weapons with custom attributes.
- Document editors: Duplicate formatted content.
- Enterprise apps: Clone preconfigured objects.
Code Examples
// Prototype interface
public interface IPrototype {
IPrototype Clone();
}
// Concrete prototype
public class Employee : IPrototype {
public string Name;
public string Role;
public Employee(string name, string role) {
Name = name;
Role = role;
}
public IPrototype Clone() {
return new Employee(Name, Role);
}
import copy
class Employee:
def __init__(self, name, role):
self.name = name
self.role = role
def clone(self):
return copy.deepcopy(self)
emp1 = Employee("Alice", "Engineer")
emp2 = emp1.clone()
Pros and Cons
- âś… Pros:
- Faster object creation: If setting up an object involves multiple steps, dependencies, or heavy computation, cloning an existing one can save significant time and resources.
- Keeps code DRY (Don’t Repeat Yourself): By reusing a well-configured prototype, you avoid redundant initialization code, making your implementation cleaner and more maintainable.
- ❌ Cons:
- Complexity: Implementing deep copies can be tricky, especially with complex object graphs or circular references.
- Memory overhead: Cloning large objects can consume more memory, especially if many copies are made.
- Not suitable for all scenarios: If the object’s state is highly dynamic or context-dependent, cloning may not be appropriate.
Pattern Comparison
Pattern | Key Idea | When to Use |
---|---|---|
Factory | Creates new instances | Encapsulate object creation logic |
Builder | Step-by-step construction | Complex configuration logic |
Prototype | Cloning objects | Want to avoid rebuilding state from scratch |
Conclusion
Use the Prototype Pattern when object creation is expensive, and you need many similar instances. It's especially helpful for runtime flexibility and dynamic systems like editors or simulation environments.