Prototype Pattern
Prototype Pattern: Cloning Objects Efficiently
Section titled “Prototype Pattern: Cloning Objects Efficiently”Now let’s dive into the Prototype Pattern - a creational design pattern that lets you create new objects by copying existing ones (prototypes) instead of creating them from scratch.
Why Prototype Pattern?
Section titled “Why Prototype Pattern?”Imagine you’re ordering custom pizzas. Instead of describing each pizza from scratch every time, you can say “I want the same as last time, but with extra cheese!” The Prototype Pattern works the same way!
The Prototype Pattern lets you create new objects by cloning existing instances. Instead of going through expensive object creation, you copy a prototype and customize it.
What’s the Use of Prototype Pattern?
Section titled “What’s the Use of Prototype Pattern?”The Prototype Pattern is useful when:
- Object creation is expensive - Database queries, network calls, complex calculations
- You need many similar objects - Objects with similar structure but different values
- You want to avoid subclassing - Don’t want to create a class hierarchy
- Runtime configuration - Objects need to be configured at runtime
- You want to hide creation complexity - Client doesn’t need to know how objects are created
What Happens If We Don’t Use Prototype Pattern?
Section titled “What Happens If We Don’t Use Prototype Pattern?”Without the Prototype Pattern, you might:
- Recreate expensive objects - Waste time and resources on expensive initialization
- Duplicate initialization code - Same setup code repeated everywhere
- Create unnecessary subclasses - Class explosion for slight variations
- Tight coupling - Client code depends on concrete classes
- Performance issues - Slow object creation impacts application performance
Simple Example: The Pizza Template System
Section titled “Simple Example: The Pizza Template System”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Prototype Pattern works in practice - showing the cloning process:
sequenceDiagram
participant Client
participant Prototype as PizzaPrototype
participant Clone1 as CustomPizza1
participant Clone2 as CustomPizza2
Note over Client,Clone2: Expensive initialization happens once
Client->>Prototype: create_prototype()
activate Prototype
Note over Prototype: Expensive setup:<br/>- Database queries<br/>- Network calls<br/>- Complex calculations
Prototype-->>Client: prototype instance
deactivate Prototype
Note over Client,Clone2: Fast cloning for similar objects
Client->>Prototype: clone()
activate Prototype
Prototype->>Clone1: create copy
activate Clone1
Clone1-->>Prototype: cloned instance
deactivate Clone1
Prototype-->>Client: Clone1
deactivate Prototype
Client->>Clone1: customize(toppings)
activate Clone1
Clone1-->>Client: customized
deactivate Clone1
Client->>Prototype: clone()
activate Prototype
Prototype->>Clone2: create copy
activate Clone2
Clone2-->>Prototype: cloned instance
deactivate Clone2
Prototype-->>Client: Clone2
deactivate Prototype
Note over Client,Clone2: Much faster than<br/>creating from scratch!
The Problem
Section titled “The Problem”You’re building a pizza ordering system where customers can order custom pizzas. Creating each pizza from scratch involves expensive operations (loading templates, fetching prices, validating ingredients). Without Prototype Pattern:
# ❌ Without Prototype Pattern - Expensive creation every time!
class Pizza: def __init__(self, size: str, crust: str, toppings: list): self.size = size self.crust = crust self.toppings = toppings # Expensive operations happen every time! self._load_template() # Database query self._fetch_prices() # Network call self._validate_ingredients() # Complex validation print(f"⏱️ Creating pizza from scratch (expensive!)")
def _load_template(self): # Simulate expensive database query import time time.sleep(0.1) # Expensive! print("📊 Loading pizza template from database...")
def _fetch_prices(self): # Simulate expensive network call import time time.sleep(0.1) # Expensive! print("🌐 Fetching prices from API...")
def _validate_ingredients(self): # Simulate complex validation import time time.sleep(0.05) # Expensive! print("✅ Validating ingredients...")
def describe(self): return f"{self.size} {self.crust} pizza with {', '.join(self.toppings)}"
# Problem: Every pizza creation is expensive!pizza1 = Pizza("large", "thin", ["cheese", "tomato"]) # Expensive!pizza2 = Pizza("large", "thin", ["cheese", "pepperoni"]) # Expensive again!pizza3 = Pizza("large", "thin", ["cheese", "mushrooms"]) # Expensive again!
# All three pizzas are similar but we pay the cost 3 times!// ❌ Without Prototype Pattern - Expensive creation every time!
public class Pizza { private String size; private String crust; private List<String> toppings;
public Pizza(String size, String crust, List<String> toppings) { this.size = size; this.crust = crust; this.toppings = toppings; // Expensive operations happen every time! loadTemplate(); // Database query fetchPrices(); // Network call validateIngredients(); // Complex validation System.out.println("⏱️ Creating pizza from scratch (expensive!)"); }
private void loadTemplate() { // Simulate expensive database query try { Thread.sleep(100); // Expensive! System.out.println("📊 Loading pizza template from database..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void fetchPrices() { // Simulate expensive network call try { Thread.sleep(100); // Expensive! System.out.println("🌐 Fetching prices from API..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void validateIngredients() { // Simulate complex validation try { Thread.sleep(50); // Expensive! System.out.println("✅ Validating ingredients..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
public String describe() { return size + " " + crust + " pizza with " + String.join(", ", toppings); }}
// Problem: Every pizza creation is expensive!public class Main { public static void main(String[] args) { Pizza pizza1 = new Pizza("large", "thin", Arrays.asList("cheese", "tomato")); // Expensive! Pizza pizza2 = new Pizza("large", "thin", Arrays.asList("cheese", "pepperoni")); // Expensive again! Pizza pizza3 = new Pizza("large", "thin", Arrays.asList("cheese", "mushrooms")); // Expensive again!
// All three pizzas are similar but we pay the cost 3 times! }}Problems:
- Expensive operations repeated for every pizza
- Slow performance when creating many similar pizzas
- Wasted resources on redundant operations
- No way to reuse expensive initialization
The Solution: Prototype Pattern
Section titled “The Solution: Prototype Pattern”Class Structure
Section titled “Class Structure”classDiagram
class PizzaPrototype {
<<abstract>>
+clone() PizzaPrototype
+customize(toppings) void
}
class Pizza {
-size: str
-crust: str
-toppings: List
+clone() Pizza
+customize(toppings) void
+describe() str
}
class MargheritaPrototype {
+clone() Pizza
}
class PepperoniPrototype {
+clone() Pizza
}
class Client {
+create_pizzas() void
}
PizzaPrototype <|-- Pizza : implements
Pizza <|-- MargheritaPrototype : extends
Pizza <|-- PepperoniPrototype : extends
Client --> PizzaPrototype : clones
note for PizzaPrototype "Expensive initialization<br/>happens once"
note for Pizza "Clone is fast -<br/>just copy data"
import copyfrom abc import ABC, abstractmethodfrom typing import List
# Step 1: Define the Prototype interfaceclass PizzaPrototype(ABC): """Prototype interface for clonable pizzas"""
@abstractmethod def clone(self) -> 'PizzaPrototype': """Create a copy of this pizza""" pass
@abstractmethod def customize(self, toppings: List[str]) -> None: """Customize the pizza""" pass
# Step 2: Create concrete Prototype classclass Pizza(PizzaPrototype): """Pizza class that can be cloned"""
def __init__(self, size: str, crust: str, toppings: List[str]): self.size = size self.crust = crust self.toppings = toppings # Expensive operations happen only once during prototype creation self._load_template() self._fetch_prices() self._validate_ingredients() print(f"⏱️ Creating pizza prototype (expensive - happens once!)")
def _load_template(self): """Expensive database query - happens once""" import time time.sleep(0.1) print("📊 Loading pizza template from database...")
def _fetch_prices(self): """Expensive network call - happens once""" import time time.sleep(0.1) print("🌐 Fetching prices from API...")
def _validate_ingredients(self): """Complex validation - happens once""" import time time.sleep(0.05) print("✅ Validating ingredients...")
def clone(self) -> 'Pizza': """Clone the pizza - fast operation!""" print(f"⚡ Cloning pizza (fast!)") # Use deep copy to create independent copy cloned = copy.deepcopy(self) return cloned
def customize(self, toppings: List[str]) -> None: """Customize the cloned pizza""" self.toppings = toppings print(f"✨ Customized with toppings: {', '.join(toppings)}")
def describe(self) -> str: return f"{self.size} {self.crust} pizza with {', '.join(self.toppings)}"
# Step 3: Create prototype instancesclass PizzaPrototypeRegistry: """Registry to store and retrieve prototypes"""
def __init__(self): self._prototypes = {}
def register(self, name: str, prototype: PizzaPrototype) -> None: """Register a prototype""" self._prototypes[name] = prototype print(f"📝 Registered prototype: {name}")
def get(self, name: str) -> PizzaPrototype: """Get a clone of the prototype""" if name not in self._prototypes: raise ValueError(f"Prototype {name} not found") prototype = self._prototypes[name] return prototype.clone()
# Step 4: Use the patterndef main(): # Create prototypes once (expensive) margherita_prototype = Pizza("large", "thin", ["cheese", "tomato"]) pepperoni_prototype = Pizza("large", "thin", ["cheese", "pepperoni"])
# Register prototypes registry = PizzaPrototypeRegistry() registry.register("margherita", margherita_prototype) registry.register("pepperoni", pepperoni_prototype)
print("\n" + "="*50) print("Creating pizzas by cloning (fast!)") print("="*50 + "\n")
# Clone pizzas (fast!) pizza1 = registry.get("margherita") pizza1.customize(["cheese", "tomato", "basil"])
pizza2 = registry.get("margherita") pizza2.customize(["cheese", "tomato", "olives"])
pizza3 = registry.get("pepperoni") pizza3.customize(["cheese", "pepperoni", "onions"])
print(f"\n🍕 Pizza 1: {pizza1.describe()}") print(f"🍕 Pizza 2: {pizza2.describe()}") print(f"🍕 Pizza 3: {pizza3.describe()}")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the Prototype interfaceinterface PizzaPrototype { PizzaPrototype clone(); void customize(List<String> toppings);}
// Step 2: Create concrete Prototype classclass Pizza implements PizzaPrototype { private String size; private String crust; private List<String> toppings;
public Pizza(String size, String crust, List<String> toppings) { this.size = size; this.crust = crust; this.toppings = new ArrayList<>(toppings); // Expensive operations happen only once during prototype creation loadTemplate(); fetchPrices(); validateIngredients(); System.out.println("⏱️ Creating pizza prototype (expensive - happens once!)"); }
// Copy constructor for cloning private Pizza(Pizza other) { this.size = other.size; this.crust = other.crust; this.toppings = new ArrayList<>(other.toppings); // No expensive operations - just copy data! }
private void loadTemplate() { // Expensive database query - happens once try { Thread.sleep(100); System.out.println("📊 Loading pizza template from database..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void fetchPrices() { // Expensive network call - happens once try { Thread.sleep(100); System.out.println("🌐 Fetching prices from API..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void validateIngredients() { // Complex validation - happens once try { Thread.sleep(50); System.out.println("✅ Validating ingredients..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
@Override public Pizza clone() { // Clone the pizza - fast operation! System.out.println("⚡ Cloning pizza (fast!)"); return new Pizza(this); }
@Override public void customize(List<String> toppings) { this.toppings = new ArrayList<>(toppings); System.out.println("✨ Customized with toppings: " + String.join(", ", toppings)); }
public String describe() { return size + " " + crust + " pizza with " + String.join(", ", toppings); }}
// Step 3: Create prototype registryclass PizzaPrototypeRegistry { private Map<String, PizzaPrototype> prototypes = new HashMap<>();
public void register(String name, PizzaPrototype prototype) { prototypes.put(name, prototype); System.out.println("📝 Registered prototype: " + name); }
public PizzaPrototype get(String name) { PizzaPrototype prototype = prototypes.get(name); if (prototype == null) { throw new IllegalArgumentException("Prototype " + name + " not found"); } return prototype.clone(); }}
// Step 4: Use the patternpublic class Main { public static void main(String[] args) { // Create prototypes once (expensive) Pizza margheritaPrototype = new Pizza("large", "thin", Arrays.asList("cheese", "tomato")); Pizza pepperoniPrototype = new Pizza("large", "thin", Arrays.asList("cheese", "pepperoni"));
// Register prototypes PizzaPrototypeRegistry registry = new PizzaPrototypeRegistry(); registry.register("margherita", margheritaPrototype); registry.register("pepperoni", pepperoniPrototype);
System.out.println("\n" + "=".repeat(50)); System.out.println("Creating pizzas by cloning (fast!)"); System.out.println("=".repeat(50) + "\n");
// Clone pizzas (fast!) Pizza pizza1 = (Pizza) registry.get("margherita"); pizza1.customize(Arrays.asList("cheese", "tomato", "basil"));
Pizza pizza2 = (Pizza) registry.get("margherita"); pizza2.customize(Arrays.asList("cheese", "tomato", "olives"));
Pizza pizza3 = (Pizza) registry.get("pepperoni"); pizza3.customize(Arrays.asList("cheese", "pepperoni", "onions"));
System.out.println("\n🍕 Pizza 1: " + pizza1.describe()); System.out.println("🍕 Pizza 2: " + pizza2.describe()); System.out.println("🍕 Pizza 3: " + pizza3.describe()); }}Real-World Software Example: Document Template System
Section titled “Real-World Software Example: Document Template System”Now let’s see a realistic software example - a document generation system where creating documents involves expensive database queries and template loading.
The Problem
Section titled “The Problem”You’re building a document generation system. Creating each document involves loading templates from database, fetching user data, and formatting. Without Prototype Pattern:
# ❌ Without Prototype Pattern - Expensive creation every time!
class Document: def __init__(self, template_name: str, user_data: dict): self.template_name = template_name self.user_data = user_data # Expensive operations every time! self._load_template() # Database query self._fetch_user_data() # Network call self._format_document() # Complex formatting print(f"⏱️ Creating document from scratch (expensive!)")
def _load_template(self): import time time.sleep(0.2) # Expensive database query print("📊 Loading template from database...")
def _fetch_user_data(self): import time time.sleep(0.15) # Expensive network call print("🌐 Fetching user data from API...")
def _format_document(self): import time time.sleep(0.1) # Complex formatting print("📝 Formatting document...")
def render(self): return f"Document: {self.template_name} for {self.user_data.get('name')}"
# Problem: Every document creation is expensive!doc1 = Document("invoice", {"name": "John", "amount": 100}) # Expensive!doc2 = Document("invoice", {"name": "Jane", "amount": 200}) # Expensive again!doc3 = Document("invoice", {"name": "Bob", "amount": 300}) # Expensive again!
# All invoices use the same template but we pay the cost 3 times!// ❌ Without Prototype Pattern - Expensive creation every time!
public class Document { private String templateName; private Map<String, Object> userData;
public Document(String templateName, Map<String, Object> userData) { this.templateName = templateName; this.userData = new HashMap<>(userData); // Expensive operations every time! loadTemplate(); // Database query fetchUserData(); // Network call formatDocument(); // Complex formatting System.out.println("⏱️ Creating document from scratch (expensive!)"); }
private void loadTemplate() { try { Thread.sleep(200); // Expensive database query System.out.println("📊 Loading template from database..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void fetchUserData() { try { Thread.sleep(150); // Expensive network call System.out.println("🌐 Fetching user data from API..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void formatDocument() { try { Thread.sleep(100); // Complex formatting System.out.println("📝 Formatting document..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
public String render() { return "Document: " + templateName + " for " + userData.get("name"); }}
// Problem: Every document creation is expensive!public class Main { public static void main(String[] args) { Map<String, Object> data1 = new HashMap<>(); data1.put("name", "John"); data1.put("amount", 100); Document doc1 = new Document("invoice", data1); // Expensive!
Map<String, Object> data2 = new HashMap<>(); data2.put("name", "Jane"); data2.put("amount", 200); Document doc2 = new Document("invoice", data2); // Expensive again!
// All invoices use the same template but we pay the cost multiple times! }}Problems:
- Expensive template loading repeated for every document
- Slow performance when generating many documents
- Wasted resources on redundant operations
- No way to reuse template loading
The Solution: Prototype Pattern
Section titled “The Solution: Prototype Pattern”import copyfrom abc import ABC, abstractmethodfrom typing import Dict, Any
# Step 1: Define the Prototype interfaceclass DocumentPrototype(ABC): """Prototype interface for clonable documents"""
@abstractmethod def clone(self) -> 'DocumentPrototype': """Create a copy of this document""" pass
@abstractmethod def customize(self, user_data: Dict[str, Any]) -> None: """Customize the document with user data""" pass
# Step 2: Create concrete Prototype classclass Document(DocumentPrototype): """Document class that can be cloned"""
def __init__(self, template_name: str, user_data: Dict[str, Any] = None): self.template_name = template_name self.user_data = user_data or {} # Expensive operations happen only once during prototype creation self._load_template() self._fetch_template_data() self._format_template() print(f"⏱️ Creating document prototype (expensive - happens once!)")
def _load_template(self): """Expensive database query - happens once""" import time time.sleep(0.2) print("📊 Loading template from database...")
def _fetch_template_data(self): """Expensive network call - happens once""" import time time.sleep(0.15) print("🌐 Fetching template data from API...")
def _format_template(self): """Complex formatting - happens once""" import time time.sleep(0.1) print("📝 Formatting template...")
def clone(self) -> 'Document': """Clone the document - fast operation!""" print(f"⚡ Cloning document (fast!)") cloned = copy.deepcopy(self) return cloned
def customize(self, user_data: Dict[str, Any]) -> None: """Customize the cloned document""" self.user_data = user_data print(f"✨ Customized with user data: {user_data.get('name')}")
def render(self) -> str: return f"Document: {self.template_name} for {self.user_data.get('name')}"
# Step 3: Create prototype registryclass DocumentPrototypeRegistry: """Registry to store and retrieve document prototypes"""
def __init__(self): self._prototypes = {}
def register(self, name: str, prototype: DocumentPrototype) -> None: """Register a prototype""" self._prototypes[name] = prototype print(f"📝 Registered document prototype: {name}")
def get(self, name: str) -> DocumentPrototype: """Get a clone of the prototype""" if name not in self._prototypes: raise ValueError(f"Prototype {name} not found") prototype = self._prototypes[name] return prototype.clone()
# Step 4: Use the patterndef main(): # Create prototypes once (expensive) invoice_prototype = Document("invoice") report_prototype = Document("report")
# Register prototypes registry = DocumentPrototypeRegistry() registry.register("invoice", invoice_prototype) registry.register("report", report_prototype)
print("\n" + "="*50) print("Generating documents by cloning (fast!)") print("="*50 + "\n")
# Clone documents (fast!) doc1 = registry.get("invoice") doc1.customize({"name": "John", "amount": 100})
doc2 = registry.get("invoice") doc2.customize({"name": "Jane", "amount": 200})
doc3 = registry.get("report") doc3.customize({"name": "Bob", "department": "Sales"})
print(f"\n📄 Document 1: {doc1.render()}") print(f"📄 Document 2: {doc2.render()}") print(f"📄 Document 3: {doc3.render()}")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the Prototype interfaceinterface DocumentPrototype { DocumentPrototype clone(); void customize(Map<String, Object> userData);}
// Step 2: Create concrete Prototype classclass Document implements DocumentPrototype { private String templateName; private Map<String, Object> userData;
public Document(String templateName) { this.templateName = templateName; this.userData = new HashMap<>(); // Expensive operations happen only once during prototype creation loadTemplate(); fetchTemplateData(); formatTemplate(); System.out.println("⏱️ Creating document prototype (expensive - happens once!)"); }
// Copy constructor for cloning private Document(Document other) { this.templateName = other.templateName; this.userData = new HashMap<>(other.userData); // No expensive operations - just copy data! }
private void loadTemplate() { try { Thread.sleep(200); // Expensive database query System.out.println("📊 Loading template from database..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void fetchTemplateData() { try { Thread.sleep(150); // Expensive network call System.out.println("🌐 Fetching template data from API..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void formatTemplate() { try { Thread.sleep(100); // Complex formatting System.out.println("📝 Formatting template..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
@Override public Document clone() { System.out.println("⚡ Cloning document (fast!)"); return new Document(this); }
@Override public void customize(Map<String, Object> userData) { this.userData = new HashMap<>(userData); System.out.println("✨ Customized with user data: " + userData.get("name")); }
public String render() { return "Document: " + templateName + " for " + userData.get("name"); }}
// Step 3: Create prototype registryclass DocumentPrototypeRegistry { private Map<String, DocumentPrototype> prototypes = new HashMap<>();
public void register(String name, DocumentPrototype prototype) { prototypes.put(name, prototype); System.out.println("📝 Registered document prototype: " + name); }
public DocumentPrototype get(String name) { DocumentPrototype prototype = prototypes.get(name); if (prototype == null) { throw new IllegalArgumentException("Prototype " + name + " not found"); } return prototype.clone(); }}
// Step 4: Use the patternpublic class Main { public static void main(String[] args) { // Create prototypes once (expensive) Document invoicePrototype = new Document("invoice"); Document reportPrototype = new Document("report");
// Register prototypes DocumentPrototypeRegistry registry = new DocumentPrototypeRegistry(); registry.register("invoice", invoicePrototype); registry.register("report", reportPrototype);
System.out.println("\n" + "=".repeat(50)); System.out.println("Generating documents by cloning (fast!)"); System.out.println("=".repeat(50) + "\n");
// Clone documents (fast!) Document doc1 = (Document) registry.get("invoice"); Map<String, Object> data1 = new HashMap<>(); data1.put("name", "John"); data1.put("amount", 100); doc1.customize(data1);
Document doc2 = (Document) registry.get("invoice"); Map<String, Object> data2 = new HashMap<>(); data2.put("name", "Jane"); data2.put("amount", 200); doc2.customize(data2);
Document doc3 = (Document) registry.get("report"); Map<String, Object> data3 = new HashMap<>(); data3.put("name", "Bob"); data3.put("department", "Sales"); doc3.customize(data3);
System.out.println("\n📄 Document 1: " + doc1.render()); System.out.println("📄 Document 2: " + doc2.render()); System.out.println("📄 Document 3: " + doc3.render()); }}Prototype Pattern Variants
Section titled “Prototype Pattern Variants”There are several ways to implement the Prototype Pattern:
1. Shallow Copy vs Deep Copy
Section titled “1. Shallow Copy vs Deep Copy”Shallow Copy: Copies object references (faster but shared references)
import copy
class Pizza: def __init__(self, toppings): self.toppings = toppings # List is mutable
def shallow_clone(self): return copy.copy(self) # Shallow copy
# Problem: Shared reference!pizza1 = Pizza(["cheese", "tomato"])pizza2 = pizza1.shallow_clone()pizza2.toppings.append("pepperoni") # Modifies pizza1.toppings too!import java.util.*;
class Pizza { private List<String> toppings;
public Pizza(List<String> toppings) { this.toppings = toppings; }
public Pizza shallowClone() { Pizza cloned = new Pizza(this.toppings); // Shallow copy - same list reference return cloned; }}
// Problem: Shared reference!Deep Copy: Copies all nested objects (slower but independent)
import copy
class Pizza: def __init__(self, toppings): self.toppings = toppings
def deep_clone(self): return copy.deepcopy(self) # Deep copy
# Solution: Independent copies!pizza1 = Pizza(["cheese", "tomato"])pizza2 = pizza1.deep_clone()pizza2.toppings.append("pepperoni") # Only modifies pizza2.toppingsimport java.util.*;
class Pizza { private List<String> toppings;
public Pizza(List<String> toppings) { this.toppings = new ArrayList<>(toppings); // Copy list }
public Pizza deepClone() { return new Pizza(new ArrayList<>(this.toppings)); // Deep copy }}
// Solution: Independent copies!2. Prototype Registry
Section titled “2. Prototype Registry”A registry pattern to manage prototypes:
class PrototypeRegistry: def __init__(self): self._prototypes = {}
def register(self, name: str, prototype): self._prototypes[name] = prototype
def get(self, name: str): return self._prototypes[name].clone()
def unregister(self, name: str): del self._prototypes[name]import java.util.*;
class PrototypeRegistry { private Map<String, Prototype> prototypes = new HashMap<>();
public void register(String name, Prototype prototype) { prototypes.put(name, prototype); }
public Prototype get(String name) { return prototypes.get(name).clone(); }
public void unregister(String name) { prototypes.remove(name); }}When to Use Prototype Pattern?
Section titled “When to Use Prototype Pattern?”Use Prototype Pattern when:
✅ Object creation is expensive - Database queries, network calls, complex calculations
✅ You need many similar objects - Objects with similar structure but different values
✅ You want to avoid subclassing - Don’t want to create a class hierarchy
✅ Runtime configuration - Objects need to be configured at runtime
✅ You want to hide creation complexity - Client doesn’t need to know how objects are created
When NOT to Use Prototype Pattern?
Section titled “When NOT to Use Prototype Pattern?”Don’t use Prototype Pattern when:
❌ Object creation is cheap - If creation is fast, cloning adds unnecessary complexity
❌ Objects are very different - If objects differ significantly, cloning doesn’t help
❌ You need deep inheritance - Subclassing might be better
❌ Cloning is complex - If cloning is as expensive as creation, pattern doesn’t help
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Shallow Copy When Deep Copy is Needed
Section titled “Mistake 1: Shallow Copy When Deep Copy is Needed”# ❌ Bad: Shallow copy with mutable objectsimport copy
class Pizza: def __init__(self, toppings): self.toppings = toppings # Mutable list!
def clone(self): return copy.copy(self) # Shallow copy - problem!
pizza1 = Pizza(["cheese"])pizza2 = pizza1.clone()pizza2.toppings.append("pepperoni") # Also modifies pizza1!
# ✅ Good: Deep copy for mutable objectsclass Pizza: def clone(self): return copy.deepcopy(self) # Deep copy - correct!// ❌ Bad: Shallow copy with mutable objectsclass Pizza { private List<String> toppings;
public Pizza clone() { return new Pizza(this.toppings); // Same list reference - problem! }}
// ✅ Good: Deep copy for mutable objectsclass Pizza { public Pizza clone() { return new Pizza(new ArrayList<>(this.toppings)); // New list - correct! }}Mistake 2: Not Implementing Clone Properly
Section titled “Mistake 2: Not Implementing Clone Properly”# ❌ Bad: Clone doesn't create independent copyclass Pizza: def clone(self): return self # Returns same instance - wrong!
# ✅ Good: Clone creates new instanceclass Pizza: def clone(self): return copy.deepcopy(self) # Creates new instance - correct!// ❌ Bad: Clone doesn't create independent copyclass Pizza { public Pizza clone() { return this; // Returns same instance - wrong! }}
// ✅ Good: Clone creates new instanceclass Pizza { public Pizza clone() { return new Pizza(this); // Creates new instance - correct! }}Mistake 3: Cloning When Creation is Cheap
Section titled “Mistake 3: Cloning When Creation is Cheap”# ❌ Bad: Cloning when creation is cheapclass SimpleObject: def __init__(self, value): self.value = value # Simple creation
def clone(self): return copy.deepcopy(self) # Unnecessary complexity!
# ✅ Better: Direct creation for simple objectsobj1 = SimpleObject(10)obj2 = SimpleObject(10) # Just create new one!// ❌ Bad: Cloning when creation is cheapclass SimpleObject { private int value;
public SimpleObject clone() { return new SimpleObject(this.value); // Unnecessary complexity! }}
// ✅ Better: Direct creation for simple objectsSimpleObject obj1 = new SimpleObject(10);SimpleObject obj2 = new SimpleObject(10); // Just create new one!Benefits of Prototype Pattern
Section titled “Benefits of Prototype Pattern”- Performance - Cloning is faster than expensive object creation
- Resource Efficiency - Reuse expensive initialization
- Flexibility - Clone and customize objects at runtime
- Reduced Subclassing - Don’t need class hierarchy for variations
- Hide Complexity - Client doesn’t need to know creation details
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Prototype Pattern?
Section titled “What is Prototype Pattern?”Prototype Pattern is a creational design pattern that lets you create new objects by copying existing instances (prototypes) instead of creating them from scratch.
Why Use It?
Section titled “Why Use It?”- ✅ Expensive object creation - Clone is faster than expensive initialization
- ✅ Many similar objects - Objects with similar structure but different values
- ✅ Avoid subclassing - Don’t need class hierarchy for variations
- ✅ Runtime configuration - Objects configured at runtime
- ✅ Resource efficiency - Reuse expensive initialization
How It Works?
Section titled “How It Works?”- Create prototype - Create an instance with expensive initialization
- Register prototype - Store prototype in registry (optional)
- Clone prototype - Create copy of prototype (fast!)
- Customize clone - Modify cloned object as needed
Key Components
Section titled “Key Components”Client → Prototype.clone() → Clone Instance- Prototype Interface - Defines clone method
- Concrete Prototype - Implements cloning
- Client - Uses prototype to create objects
- Registry - Manages prototypes (optional)
Simple Example
Section titled “Simple Example”class Pizza: def __init__(self): # Expensive initialization self._load_template()
def clone(self): return copy.deepcopy(self) # Fast clone!
# Usageprototype = Pizza() # Expensive - happens oncepizza1 = prototype.clone() # Fast!pizza2 = prototype.clone() # Fast!When to Use?
Section titled “When to Use?”✅ Expensive object creation
✅ Many similar objects needed
✅ Want to avoid subclassing
✅ Runtime configuration required
✅ Need to hide creation complexity
When NOT to Use?
Section titled “When NOT to Use?”❌ Object creation is cheap
❌ Objects are very different
❌ Need deep inheritance
❌ Cloning is as expensive as creation
Key Takeaways
Section titled “Key Takeaways”- Prototype Pattern = Clone existing objects instead of creating new
- Prototype = Template object with expensive initialization
- Clone = Fast copy operation
- Benefit = Performance and resource efficiency
- Use Case = Expensive object creation with similar objects
Common Pattern Structure
Section titled “Common Pattern Structure”class Prototype: def clone(self): return copy.deepcopy(self)
# Usageprototype = Prototype() # Expensive initializationclone1 = prototype.clone() # Fast!clone2 = prototype.clone() # Fast!Remember
Section titled “Remember”- Prototype Pattern clones existing objects
- It’s useful when creation is expensive
- Use deep copy for objects with mutable nested structures
- Clone is faster than expensive initialization
- Don’t use it when creation is cheap - avoid over-engineering!
Interview Focus: Prototype Pattern
Section titled “Interview Focus: Prototype Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Prototype Pattern is a creational design pattern that lets you create new objects by copying existing instances instead of creating them from scratch. It’s useful when object creation is expensive, and you need many similar objects.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of when to use it
- Indicates you can explain concepts clearly
2. When to Use Prototype Pattern
Section titled “2. When to Use Prototype Pattern”Must mention:
- ✅ Expensive object creation - Database queries, network calls
- ✅ Many similar objects - Objects with similar structure
- ✅ Avoid subclassing - Don’t want class hierarchy
- ✅ Runtime configuration - Objects configured at runtime
Example scenario to give:
“I’d use Prototype Pattern when building a document generation system. Loading templates from database is expensive. Instead of loading the template every time, I create a prototype once, then clone it for each document with different user data.”
3. Shallow vs Deep Copy
Section titled “3. Shallow vs Deep Copy”Must discuss:
- Shallow Copy: Copies object references (faster but shared references)
- Deep Copy: Copies all nested objects (slower but independent)
- When to use each: Deep copy for mutable nested structures
Example to give:
“In Python, I use
copy.deepcopy()for objects with mutable nested structures like lists or dictionaries. Shallow copy would share references, causing bugs when modifying cloned objects.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Performance - Cloning is faster than expensive creation
- Resource Efficiency - Reuse expensive initialization
- Flexibility - Clone and customize at runtime
- Reduced Subclassing - Don’t need class hierarchy
Trade-offs to acknowledge:
- Complexity - Need to implement cloning correctly
- Memory - Prototypes consume memory
- Not always faster - If creation is cheap, cloning adds overhead
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Prototype Pattern and Factory Pattern?”
A:
“Factory Pattern creates objects based on input, while Prototype Pattern clones existing objects. Factory is used when you need different types of objects, Prototype is used when you need many similar objects and creation is expensive.”
Q: “When would you use shallow copy vs deep copy?”
A:
“I use shallow copy when objects don’t have mutable nested structures - it’s faster. I use deep copy when objects have mutable nested structures like lists or dictionaries - it ensures independent copies and prevents bugs from shared references.”
Q: “How do you implement Prototype Pattern in a multi-threaded environment?”
A:
“I ensure the clone method is thread-safe. The prototype itself should be immutable or protected with synchronization. When cloning, I create a new instance with copied data, which is naturally thread-safe since each clone is independent.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Prototype Pattern clearly in one sentence
- Explain when to use it (with examples)
- Describe shallow vs deep copy
- Implement Prototype Pattern from scratch
- Compare with other creational patterns
- List benefits and trade-offs
- Identify common mistakes
- Give 2-3 real-world examples
- Discuss thread safety considerations
Remember: Prototype Pattern is about cloning expensive objects efficiently - clone once, customize many times! 🎯