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:
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! }}// ❌ Without Prototype Pattern - Expensive creation every time!
class Pizza { size: string; crust: string; toppings: string[];
constructor(size: string, crust: string, toppings: string[]) { this.size = size; this.crust = crust; this.toppings = toppings; // Expensive operations happen every time! this.loadTemplate(); // Database query this.fetchPrices(); // Network call this.validateIngredients(); // Complex validation console.log("⏱️ Creating pizza from scratch (expensive!)"); }
private loadTemplate(): void { // Simulate expensive database query const start = Date.now(); while (Date.now() - start < 100) {} // Expensive! console.log("📊 Loading pizza template from database..."); }
private fetchPrices(): void { // Simulate expensive network call const start = Date.now(); while (Date.now() - start < 100) {} // Expensive! console.log("🌐 Fetching prices from API..."); }
private validateIngredients(): void { // Simulate complex validation const start = Date.now(); while (Date.now() - start < 50) {} // Expensive! console.log("✅ Validating ingredients..."); }
describe(): string { return `${this.size} ${this.crust} pizza with ${this.toppings.join(', ')}`; }}
// Problem: Every pizza creation is expensive!const pizza1 = new Pizza("large", "thin", ["cheese", "tomato"]); // Expensive!const pizza2 = new Pizza("large", "thin", ["cheese", "pepperoni"]); // Expensive again!const pizza3 = new 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!
#include <iostream>#include <string>#include <vector>#include <thread>#include <chrono>
class Pizza {private: std::string size; std::string crust; std::vector<std::string> toppings;
void loadTemplate() { // Simulate expensive database query std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Expensive! std::cout << "📊 Loading pizza template from database..." << std::endl; }
void fetchPrices() { // Simulate expensive network call std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Expensive! std::cout << "🌐 Fetching prices from API..." << std::endl; }
void validateIngredients() { // Simulate complex validation std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Expensive! std::cout << "✅ Validating ingredients..." << std::endl; }
public: Pizza(const std::string& size, const std::string& crust, const std::vector<std::string>& toppings) : size(size), crust(crust), toppings(toppings) { // Expensive operations happen every time! loadTemplate(); // Database query fetchPrices(); // Network call validateIngredients(); // Complex validation std::cout << "⏱️ Creating pizza from scratch (expensive!)" << std::endl; }
std::string describe() const { std::string result = size + " " + crust + " pizza with "; for (size_t i = 0; i < toppings.size(); ++i) { if (i > 0) result += ", "; result += toppings[i]; } return result; }};
// Problem: Every pizza creation is expensive!int main() { Pizza pizza1("large", "thin", {"cheese", "tomato"}); // Expensive! Pizza pizza2("large", "thin", {"cheese", "pepperoni"}); // Expensive again! Pizza pizza3("large", "thin", {"cheese", "mushrooms"}); // Expensive again!
// All three pizzas are similar but we pay the cost 3 times! return 0;}// ❌ Without Prototype Pattern - Expensive creation every time!
using System;using System.Collections.Generic;using System.Threading;
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 Console.WriteLine("⏱️ Creating pizza from scratch (expensive!)"); }
private void LoadTemplate() { // Simulate expensive database query Thread.Sleep(100); // Expensive! Console.WriteLine("📊 Loading pizza template from database..."); }
private void FetchPrices() { // Simulate expensive network call Thread.Sleep(100); // Expensive! Console.WriteLine("🌐 Fetching prices from API..."); }
private void ValidateIngredients() { // Simulate complex validation Thread.Sleep(50); // Expensive! Console.WriteLine("✅ Validating ingredients..."); }
public string Describe() { return $"{size} {crust} pizza with {string.Join(", ", toppings)}"; }}
// Problem: Every pizza creation is expensive!class Program{ static void Main() { var pizza1 = new Pizza("large", "thin", new List<string> { "cheese", "tomato" }); // Expensive! var pizza2 = new Pizza("large", "thin", new List<string> { "cheese", "pepperoni" }); // Expensive again! var pizza3 = new Pizza("large", "thin", new List<string> { "cheese", "mushrooms" }); // Expensive again!
// All three pizzas are similar but we pay the cost 3 times! }}// ❌ Without Prototype Pattern - Expensive creation every time!
package main
import ( "fmt" "strings" "time")
type Pizza struct { size string crust string toppings []string}
func NewPizza(size, crust string, toppings []string) *Pizza { p := &Pizza{size: size, crust: crust, toppings: append([]string(nil), toppings...)} // Expensive operations happen every time! p.loadTemplate() // Database query p.fetchPrices() // Network call p.validateIngredients() // Complex validation fmt.Println("⏱️ Creating pizza from scratch (expensive!)") return p}
func (p *Pizza) loadTemplate() { time.Sleep(100 * time.Millisecond) // Expensive! fmt.Println("📊 Loading pizza template from database...")}
func (p *Pizza) fetchPrices() { time.Sleep(100 * time.Millisecond) // Expensive! fmt.Println("🌐 Fetching prices from API...")}
func (p *Pizza) validateIngredients() { time.Sleep(50 * time.Millisecond) // Expensive! fmt.Println("✅ Validating ingredients...")}
func (p *Pizza) Describe() string { return fmt.Sprintf("%s %s pizza with %s", p.size, p.crust, strings.Join(p.toppings, ", "))}
// Problem: Every pizza creation is expensive!func main() { pizza1 := NewPizza("large", "thin", []string{"cheese", "tomato"}) // Expensive! pizza2 := NewPizza("large", "thin", []string{"cheese", "pepperoni"}) // Expensive again! pizza3 := NewPizza("large", "thin", []string{"cheese", "mushrooms"}) // Expensive again!
// All three pizzas are similar but we pay the cost 3 times! _ = pizza1 _ = pizza2 _ = pizza3}// The Problemstruct Document { title: String, body: String, tags: Vec<String>,}fn duplicate(original: &Document) -> Document { Document { title: original.title.clone(), body: original.body.clone(), tags: original.tags.clone(), }}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”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()); }}// Step 1: Define the Prototype interfaceinterface PizzaPrototype { clone(): PizzaPrototype; customize(toppings: string[]): void;}
// Step 2: Create concrete Prototype classclass Pizza implements PizzaPrototype { private size: string; private crust: string; private toppings: string[];
constructor(size: string, crust: string, toppings: string[]) { this.size = size; this.crust = crust; this.toppings = [...toppings]; // Expensive operations happen only once during prototype creation this.loadTemplate(); this.fetchPrices(); this.validateIngredients(); console.log("⏱️ Creating pizza prototype (expensive - happens once!)"); }
// Copy constructor for cloning private constructor_copy(other: Pizza) { this.size = other.size; this.crust = other.crust; this.toppings = [...other.toppings]; }
private loadTemplate(): void { const start = Date.now(); while (Date.now() - start < 100) {} console.log("📊 Loading pizza template from database..."); }
private fetchPrices(): void { const start = Date.now(); while (Date.now() - start < 100) {} console.log("🌐 Fetching prices from API..."); }
private validateIngredients(): void { const start = Date.now(); while (Date.now() - start < 50) {} console.log("✅ Validating ingredients..."); }
clone(): Pizza { console.log("⚡ Cloning pizza (fast!)"); const cloned = Object.create(Object.getPrototypeOf(this)); cloned.size = this.size; cloned.crust = this.crust; cloned.toppings = [...this.toppings]; return cloned; }
customize(toppings: string[]): void { this.toppings = [...toppings]; console.log(`✨ Customized with toppings: ${toppings.join(', ')}`); }
describe(): string { return `${this.size} ${this.crust} pizza with ${this.toppings.join(', ')}`; }}
// Step 3: Create prototype registryclass PizzaPrototypeRegistry { private prototypes: Map<string, PizzaPrototype> = new Map();
register(name: string, prototype: PizzaPrototype): void { this.prototypes.set(name, prototype); console.log(`📝 Registered prototype: ${name}`); }
get(name: string): PizzaPrototype { const prototype = this.prototypes.get(name); if (!prototype) { throw new Error(`Prototype ${name} not found`); } return prototype.clone(); }}
// Step 4: Use the patternfunction main(): void { // Create prototypes once (expensive) const margheritaPrototype = new Pizza("large", "thin", ["cheese", "tomato"]); const pepperoniPrototype = new Pizza("large", "thin", ["cheese", "pepperoni"]);
// Register prototypes const registry = new PizzaPrototypeRegistry(); registry.register("margherita", margheritaPrototype); registry.register("pepperoni", pepperoniPrototype);
console.log("\n" + "=".repeat(50)); console.log("Creating pizzas by cloning (fast!)"); console.log("=".repeat(50) + "\n");
// Clone pizzas (fast!) const pizza1 = registry.get("margherita") as Pizza; pizza1.customize(["cheese", "tomato", "basil"]);
const pizza2 = registry.get("margherita") as Pizza; pizza2.customize(["cheese", "tomato", "olives"]);
const pizza3 = registry.get("pepperoni") as Pizza; pizza3.customize(["cheese", "pepperoni", "onions"]);
console.log(`\n🍕 Pizza 1: ${pizza1.describe()}`); console.log(`🍕 Pizza 2: ${pizza2.describe()}`); console.log(`🍕 Pizza 3: ${pizza3.describe()}`);}
main();#include <iostream>#include <string>#include <vector>#include <map>#include <memory>#include <thread>#include <chrono>#include <algorithm>
// Step 1: Define the Prototype interfaceclass PizzaPrototype {public: virtual ~PizzaPrototype() = default; virtual std::unique_ptr<PizzaPrototype> clone() = 0; virtual void customize(const std::vector<std::string>& toppings) = 0; virtual std::string describe() const = 0;};
// Step 2: Create concrete Prototype classclass Pizza : public PizzaPrototype {private: std::string size; std::string crust; std::vector<std::string> toppings;
void loadTemplate() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "📊 Loading pizza template from database..." << std::endl; }
void fetchPrices() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "🌐 Fetching prices from API..." << std::endl; }
void validateIngredients() { std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::cout << "✅ Validating ingredients..." << std::endl; }
public: Pizza(const std::string& size, const std::string& crust, const std::vector<std::string>& toppings) : size(size), crust(crust), toppings(toppings) { // Expensive operations happen only once during prototype creation loadTemplate(); fetchPrices(); validateIngredients(); std::cout << "⏱️ Creating pizza prototype (expensive - happens once!)" << std::endl; }
// Copy constructor for cloning Pizza(const Pizza& other) : size(other.size), crust(other.crust), toppings(other.toppings) { std::cout << "⚡ Cloning pizza (fast!)" << std::endl; }
std::unique_ptr<PizzaPrototype> clone() override { return std::make_unique<Pizza>(*this); }
void customize(const std::vector<std::string>& toppings) override { this->toppings = toppings; std::cout << "✨ Customized with toppings: "; for (size_t i = 0; i < toppings.size(); ++i) { if (i > 0) std::cout << ", "; std::cout << toppings[i]; } std::cout << std::endl; }
std::string describe() const override { std::string result = size + " " + crust + " pizza with "; for (size_t i = 0; i < toppings.size(); ++i) { if (i > 0) result += ", "; result += toppings[i]; } return result; }};
// Step 3: Create prototype registryclass PizzaPrototypeRegistry {private: std::map<std::string, std::unique_ptr<PizzaPrototype>> prototypes;
public: void registerPrototype(const std::string& name, std::unique_ptr<PizzaPrototype> prototype) { prototypes[name] = std::move(prototype); std::cout << "📝 Registered prototype: " << name << std::endl; }
std::unique_ptr<PizzaPrototype> get(const std::string& name) { auto it = prototypes.find(name); if (it == prototypes.end()) { throw std::invalid_argument("Prototype " + name + " not found"); } return it->second->clone(); }};
// Step 4: Use the patternint main() { // Create prototypes once (expensive) auto margheritaPrototype = std::make_unique<Pizza>("large", "thin", std::vector<std::string>{"cheese", "tomato"}); auto pepperoniPrototype = std::make_unique<Pizza>("large", "thin", std::vector<std::string>{"cheese", "pepperoni"});
// Register prototypes PizzaPrototypeRegistry registry; registry.registerPrototype("margherita", std::move(margheritaPrototype)); registry.registerPrototype("pepperoni", std::move(pepperoniPrototype));
std::cout << "\n" << std::string(50, '=') << std::endl; std::cout << "Creating pizzas by cloning (fast!)" << std::endl; std::cout << std::string(50, '=') << "\n" << std::endl;
// Clone pizzas (fast!) auto pizza1 = registry.get("margherita"); pizza1->customize({"cheese", "tomato", "basil"});
auto pizza2 = registry.get("margherita"); pizza2->customize({"cheese", "tomato", "olives"});
auto pizza3 = registry.get("pepperoni"); pizza3->customize({"cheese", "pepperoni", "onions"});
std::cout << "\n🍕 Pizza 1: " << pizza1->describe() << std::endl; std::cout << "🍕 Pizza 2: " << pizza2->describe() << std::endl; std::cout << "🍕 Pizza 3: " << pizza3->describe() << std::endl;
return 0;}using System;using System.Collections.Generic;using System.Linq;using System.Threading;
// Step 1: Define the Prototype interfacepublic interface IPizzaPrototype{ IPizzaPrototype Clone(); void Customize(List<string> toppings); string Describe();}
// Step 2: Create concrete Prototype classpublic class Pizza : IPizzaPrototype{ 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 List<string>(toppings); // Expensive operations happen only once during prototype creation LoadTemplate(); FetchPrices(); ValidateIngredients(); Console.WriteLine("⏱️ 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 List<string>(other.toppings); Console.WriteLine("⚡ Cloning pizza (fast!)"); }
private void LoadTemplate() { Thread.Sleep(100); Console.WriteLine("📊 Loading pizza template from database..."); }
private void FetchPrices() { Thread.Sleep(100); Console.WriteLine("🌐 Fetching prices from API..."); }
private void ValidateIngredients() { Thread.Sleep(50); Console.WriteLine("✅ Validating ingredients..."); }
public IPizzaPrototype Clone() { return new Pizza(this); }
public void Customize(List<string> toppings) { this.toppings = new List<string>(toppings); Console.WriteLine($"✨ Customized with toppings: {string.Join(", ", toppings)}"); }
public string Describe() { return $"{size} {crust} pizza with {string.Join(", ", toppings)}"; }}
// Step 3: Create prototype registrypublic class PizzaPrototypeRegistry{ private Dictionary<string, IPizzaPrototype> prototypes = new Dictionary<string, IPizzaPrototype>();
public void Register(string name, IPizzaPrototype prototype) { prototypes[name] = prototype; Console.WriteLine($"📝 Registered prototype: {name}"); }
public IPizzaPrototype Get(string name) { if (!prototypes.ContainsKey(name)) { throw new ArgumentException($"Prototype {name} not found"); } return prototypes[name].Clone(); }}
// Step 4: Use the patternclass Program{ static void Main() { // Create prototypes once (expensive) var margheritaPrototype = new Pizza("large", "thin", new List<string> { "cheese", "tomato" }); var pepperoniPrototype = new Pizza("large", "thin", new List<string> { "cheese", "pepperoni" });
// Register prototypes var registry = new PizzaPrototypeRegistry(); registry.Register("margherita", margheritaPrototype); registry.Register("pepperoni", pepperoniPrototype);
Console.WriteLine("\n" + new string('=', 50)); Console.WriteLine("Creating pizzas by cloning (fast!)"); Console.WriteLine(new string('=', 50) + "\n");
// Clone pizzas (fast!) var pizza1 = (Pizza)registry.Get("margherita"); pizza1.Customize(new List<string> { "cheese", "tomato", "basil" });
var pizza2 = (Pizza)registry.Get("margherita"); pizza2.Customize(new List<string> { "cheese", "tomato", "olives" });
var pizza3 = (Pizza)registry.Get("pepperoni"); pizza3.Customize(new List<string> { "cheese", "pepperoni", "onions" });
Console.WriteLine($"\n🍕 Pizza 1: {pizza1.Describe()}"); Console.WriteLine($"🍕 Pizza 2: {pizza2.Describe()}"); Console.WriteLine($"🍕 Pizza 3: {pizza3.Describe()}"); }}package main
import ( "errors" "fmt" "strings" "time")
// Step 1: Define the Prototype interfacetype PizzaPrototype interface { Clone() PizzaPrototype Customize(toppings []string) Describe() string}
// Step 2: Create concrete Prototype structtype Pizza struct { size string crust string toppings []string}
func NewPizza(size, crust string, toppings []string) *Pizza { p := &Pizza{size: size, crust: crust, toppings: append([]string(nil), toppings...)} // Expensive operations happen only once during prototype creation p.loadTemplate() p.fetchPrices() p.validateIngredients() fmt.Println("⏱️ Creating pizza prototype (expensive - happens once!)") return p}
func (p *Pizza) loadTemplate() { time.Sleep(100 * time.Millisecond) fmt.Println("📊 Loading pizza template from database...")}
func (p *Pizza) fetchPrices() { time.Sleep(100 * time.Millisecond) fmt.Println("🌐 Fetching prices from API...")}
func (p *Pizza) validateIngredients() { time.Sleep(50 * time.Millisecond) fmt.Println("✅ Validating ingredients...")}
func (p *Pizza) Clone() PizzaPrototype { fmt.Println("⚡ Cloning pizza (fast!)") return &Pizza{ size: p.size, crust: p.crust, toppings: append([]string(nil), p.toppings...), }}
func (p *Pizza) Customize(toppings []string) { p.toppings = append([]string(nil), toppings...) fmt.Printf("✨ Customized with toppings: %s\n", strings.Join(toppings, ", "))}
func (p *Pizza) Describe() string { return fmt.Sprintf("%s %s pizza with %s", p.size, p.crust, strings.Join(p.toppings, ", "))}
// Step 3: Prototype registrytype PizzaPrototypeRegistry struct { prototypes map[string]PizzaPrototype}
func NewPizzaPrototypeRegistry() *PizzaPrototypeRegistry { return &PizzaPrototypeRegistry{prototypes: make(map[string]PizzaPrototype)}}
func (r *PizzaPrototypeRegistry) Register(name string, prototype PizzaPrototype) { r.prototypes[name] = prototype fmt.Printf("📝 Registered prototype: %s\n", name)}
func (r *PizzaPrototypeRegistry) Get(name string) (PizzaPrototype, error) { prototype, ok := r.prototypes[name] if !ok { return nil, errors.New("prototype " + name + " not found") } return prototype.Clone(), nil}
// Step 4: Use the patternfunc main() { // Create prototypes once (expensive) margheritaPrototype := NewPizza("large", "thin", []string{"cheese", "tomato"}) pepperoniPrototype := NewPizza("large", "thin", []string{"cheese", "pepperoni"})
// Register prototypes registry := NewPizzaPrototypeRegistry() registry.Register("margherita", margheritaPrototype) registry.Register("pepperoni", pepperoniPrototype)
fmt.Println("\n" + strings.Repeat("=", 50)) fmt.Println("Creating pizzas by cloning (fast!)") fmt.Println(strings.Repeat("=", 50) + "\n")
// Clone pizzas (fast!) pizza1, _ := registry.Get("margherita") pizza1.Customize([]string{"cheese", "tomato", "basil"})
pizza2, _ := registry.Get("margherita") pizza2.Customize([]string{"cheese", "tomato", "olives"})
pizza3, _ := registry.Get("pepperoni") pizza3.Customize([]string{"cheese", "pepperoni", "onions"})
fmt.Printf("\n🍕 Pizza 1: %s\n", pizza1.Describe()) fmt.Printf("🍕 Pizza 2: %s\n", pizza2.Describe()) fmt.Printf("🍕 Pizza 3: %s\n", pizza3.Describe())}// Class Structure#[derive(Clone)]struct Document { title: String, body: String, tags: Vec<String>,}fn main() { let template = Document { title: "Template".into(), body: "Reusable".into(), tags: vec!["draft".into()], }; let copy = template.clone(); println!("{}", copy.title);}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! }}// ❌ Without Prototype Pattern - Expensive creation every time!
class Document { private templateName: string; private userData: Record<string, any>;
constructor(templateName: string, userData: Record<string, any>) { this.templateName = templateName; this.userData = {...userData}; // Expensive operations every time! this.loadTemplate(); // Database query this.fetchUserData(); // Network call this.formatDocument(); // Complex formatting console.log("⏱️ Creating document from scratch (expensive!)"); }
private loadTemplate(): void { const start = Date.now(); while (Date.now() - start < 200) {} // Expensive database query console.log("📊 Loading template from database..."); }
private fetchUserData(): void { const start = Date.now(); while (Date.now() - start < 150) {} // Expensive network call console.log("🌐 Fetching user data from API..."); }
private formatDocument(): void { const start = Date.now(); while (Date.now() - start < 100) {} // Complex formatting console.log("📝 Formatting document..."); }
render(): string { return `Document: ${this.templateName} for ${this.userData.name}`; }}
// Problem: Every document creation is expensive!const doc1 = new Document("invoice", {name: "John", amount: 100}); // Expensive!const doc2 = new Document("invoice", {name: "Jane", amount: 200}); // Expensive again!const doc3 = new 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!
#include <iostream>#include <string>#include <map>#include <thread>#include <chrono>
class Document {private: std::string templateName; std::map<std::string, std::string> userData;
void loadTemplate() { std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Expensive database query std::cout << "📊 Loading template from database..." << std::endl; }
void fetchUserData() { std::this_thread::sleep_for(std::chrono::milliseconds(150)); // Expensive network call std::cout << "🌐 Fetching user data from API..." << std::endl; }
void formatDocument() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Complex formatting std::cout << "📝 Formatting document..." << std::endl; }
public: Document(const std::string& templateName, const std::map<std::string, std::string>& userData) : templateName(templateName), userData(userData) { // Expensive operations every time! loadTemplate(); // Database query fetchUserData(); // Network call formatDocument(); // Complex formatting std::cout << "⏱️ Creating document from scratch (expensive!)" << std::endl; }
std::string render() const { return "Document: " + templateName + " for " + userData.at("name"); }};
// Problem: Every document creation is expensive!int main() { Document doc1("invoice", {{"name", "John"}, {"amount", "100"}}); // Expensive! Document doc2("invoice", {{"name", "Jane"}, {"amount", "200"}}); // Expensive again! Document doc3("invoice", {{"name", "Bob"}, {"amount", "300"}}); // Expensive again!
// All invoices use the same template but we pay the cost 3 times! return 0;}// ❌ Without Prototype Pattern - Expensive creation every time!
using System;using System.Collections.Generic;using System.Threading;
public class Document{ private string templateName; private Dictionary<string, object> userData;
public Document(string templateName, Dictionary<string, object> userData) { this.templateName = templateName; this.userData = new Dictionary<string, object>(userData); // Expensive operations every time! LoadTemplate(); // Database query FetchUserData(); // Network call FormatDocument(); // Complex formatting Console.WriteLine("⏱️ Creating document from scratch (expensive!)"); }
private void LoadTemplate() { Thread.Sleep(200); // Expensive database query Console.WriteLine("📊 Loading template from database..."); }
private void FetchUserData() { Thread.Sleep(150); // Expensive network call Console.WriteLine("🌐 Fetching user data from API..."); }
private void FormatDocument() { Thread.Sleep(100); // Complex formatting Console.WriteLine("📝 Formatting document..."); }
public string Render() { return $"Document: {templateName} for {userData["name"]}"; }}
// Problem: Every document creation is expensive!class Program{ static void Main() { var doc1 = new Document("invoice", new Dictionary<string, object> { { "name", "John" }, { "amount", 100 } }); // Expensive! var doc2 = new Document("invoice", new Dictionary<string, object> { { "name", "Jane" }, { "amount", 200 } }); // Expensive again! var doc3 = new Document("invoice", new Dictionary<string, object> { { "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!
package main
import ( "fmt" "time")
type Document struct { templateName string userData map[string]any}
func NewDocument(templateName string, userData map[string]any) *Document { d := &Document{templateName: templateName, userData: userData} // Expensive operations every time! d.loadTemplate() // Database query d.fetchUserData() // Network call d.formatDocument() // Complex formatting fmt.Println("⏱️ Creating document from scratch (expensive!)") return d}
func (d *Document) loadTemplate() { time.Sleep(200 * time.Millisecond) // Expensive database query fmt.Println("📊 Loading template from database...")}
func (d *Document) fetchUserData() { time.Sleep(150 * time.Millisecond) // Expensive network call fmt.Println("🌐 Fetching user data from API...")}
func (d *Document) formatDocument() { time.Sleep(100 * time.Millisecond) // Complex formatting fmt.Println("📝 Formatting document...")}
func (d *Document) Render() string { return fmt.Sprintf("Document: %s for %v", d.templateName, d.userData["name"])}
// Problem: Every document creation is expensive!func main() { doc1 := NewDocument("invoice", map[string]any{"name": "John", "amount": 100}) // Expensive! doc2 := NewDocument("invoice", map[string]any{"name": "Jane", "amount": 200}) // Expensive again! doc3 := NewDocument("invoice", map[string]any{"name": "Bob", "amount": 300}) // Expensive again!
// All invoices use the same template but we pay the cost 3 times! _ = doc1 _ = doc2 _ = doc3}// The Problemstruct Document { title: String, body: String, tags: Vec<String>,}fn duplicate(original: &Document) -> Document { Document { title: original.title.clone(), body: original.body.clone(), tags: original.tags.clone(), }}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()); }}// Step 1: Define the Prototype interfaceinterface DocumentPrototype { clone(): DocumentPrototype; customize(userData: Record<string, any>): void; render(): string;}
// Step 2: Create concrete Prototype classclass Document implements DocumentPrototype { private templateName: string; private userData: Record<string, any>;
constructor(templateName: string) { this.templateName = templateName; this.userData = {}; // Expensive operations happen only once during prototype creation this.loadTemplate(); this.fetchTemplateData(); this.formatTemplate(); console.log("⏱️ Creating document prototype (expensive - happens once!)"); }
// Copy constructor for cloning private constructor_copy(other: Document) { this.templateName = other.templateName; this.userData = {...other.userData}; }
private loadTemplate(): void { const start = Date.now(); while (Date.now() - start < 200) {} // Expensive database query console.log("📊 Loading template from database..."); }
private fetchTemplateData(): void { const start = Date.now(); while (Date.now() - start < 150) {} // Expensive network call console.log("🌐 Fetching template data from API..."); }
private formatTemplate(): void { const start = Date.now(); while (Date.now() - start < 100) {} // Complex formatting console.log("📝 Formatting template..."); }
clone(): Document { console.log("⚡ Cloning document (fast!)"); const cloned = Object.create(Object.getPrototypeOf(this)); cloned.templateName = this.templateName; cloned.userData = {...this.userData}; return cloned; }
customize(userData: Record<string, any>): void { this.userData = {...userData}; console.log(`✨ Customized with user data: ${userData.name}`); }
render(): string { return `Document: ${this.templateName} for ${this.userData.name}`; }}
// Step 3: Create prototype registryclass DocumentPrototypeRegistry { private prototypes: Map<string, DocumentPrototype> = new Map();
register(name: string, prototype: DocumentPrototype): void { this.prototypes.set(name, prototype); console.log(`📝 Registered document prototype: ${name}`); }
get(name: string): DocumentPrototype { const prototype = this.prototypes.get(name); if (!prototype) { throw new Error(`Prototype ${name} not found`); } return prototype.clone(); }}
// Step 4: Use the patternfunction main(): void { // Create prototypes once (expensive) const invoicePrototype = new Document("invoice"); const reportPrototype = new Document("report");
// Register prototypes const registry = new DocumentPrototypeRegistry(); registry.register("invoice", invoicePrototype); registry.register("report", reportPrototype);
console.log("\n" + "=".repeat(50)); console.log("Generating documents by cloning (fast!)"); console.log("=".repeat(50) + "\n");
// Clone documents (fast!) const doc1 = registry.get("invoice") as Document; doc1.customize({name: "John", amount: 100});
const doc2 = registry.get("invoice") as Document; doc2.customize({name: "Jane", amount: 200});
const doc3 = registry.get("report") as Document; doc3.customize({name: "Bob", department: "Sales"});
console.log(`\n📄 Document 1: ${doc1.render()}`); console.log(`📄 Document 2: ${doc2.render()}`); console.log(`📄 Document 3: ${doc3.render()}`);}
main();#include <iostream>#include <string>#include <map>#include <memory>#include <thread>#include <chrono>
// Step 1: Define the Prototype interfaceclass DocumentPrototype {public: virtual ~DocumentPrototype() = default; virtual std::unique_ptr<DocumentPrototype> clone() = 0; virtual void customize(const std::map<std::string, std::string>& userData) = 0; virtual std::string render() const = 0;};
// Step 2: Create concrete Prototype classclass Document : public DocumentPrototype {private: std::string templateName; std::map<std::string, std::string> userData;
void loadTemplate() { std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Expensive database query std::cout << "📊 Loading template from database..." << std::endl; }
void fetchTemplateData() { std::this_thread::sleep_for(std::chrono::milliseconds(150)); // Expensive network call std::cout << "🌐 Fetching template data from API..." << std::endl; }
void formatTemplate() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Complex formatting std::cout << "📝 Formatting template..." << std::endl; }
public: Document(const std::string& templateName) : templateName(templateName) { // Expensive operations happen only once during prototype creation loadTemplate(); fetchTemplateData(); formatTemplate(); std::cout << "⏱️ Creating document prototype (expensive - happens once!)" << std::endl; }
// Copy constructor for cloning Document(const Document& other) : templateName(other.templateName), userData(other.userData) { std::cout << "⚡ Cloning document (fast!)" << std::endl; }
std::unique_ptr<DocumentPrototype> clone() override { return std::make_unique<Document>(*this); }
void customize(const std::map<std::string, std::string>& userData) override { this->userData = userData; std::cout << "✨ Customized with user data: " << userData.at("name") << std::endl; }
std::string render() const override { return "Document: " + templateName + " for " + userData.at("name"); }};
// Step 3: Create prototype registryclass DocumentPrototypeRegistry {private: std::map<std::string, std::unique_ptr<DocumentPrototype>> prototypes;
public: void registerPrototype(const std::string& name, std::unique_ptr<DocumentPrototype> prototype) { prototypes[name] = std::move(prototype); std::cout << "📝 Registered document prototype: " << name << std::endl; }
std::unique_ptr<DocumentPrototype> get(const std::string& name) { auto it = prototypes.find(name); if (it == prototypes.end()) { throw std::invalid_argument("Prototype " + name + " not found"); } return it->second->clone(); }};
// Step 4: Use the patternint main() { // Create prototypes once (expensive) auto invoicePrototype = std::make_unique<Document>("invoice"); auto reportPrototype = std::make_unique<Document>("report");
// Register prototypes DocumentPrototypeRegistry registry; registry.registerPrototype("invoice", std::move(invoicePrototype)); registry.registerPrototype("report", std::move(reportPrototype));
std::cout << "\n" << std::string(50, '=') << std::endl; std::cout << "Generating documents by cloning (fast!)" << std::endl; std::cout << std::string(50, '=') << "\n" << std::endl;
// Clone documents (fast!) auto doc1 = registry.get("invoice"); doc1->customize({{"name", "John"}, {"amount", "100"}});
auto doc2 = registry.get("invoice"); doc2->customize({{"name", "Jane"}, {"amount", "200"}});
auto doc3 = registry.get("report"); doc3->customize({{"name", "Bob"}, {"department", "Sales"}});
std::cout << "\n📄 Document 1: " << doc1->render() << std::endl; std::cout << "📄 Document 2: " << doc2->render() << std::endl; std::cout << "📄 Document 3: " << doc3->render() << std::endl;
return 0;}using System;using System.Collections.Generic;using System.Threading;
// Step 1: Define the Prototype interfacepublic interface IDocumentPrototype{ IDocumentPrototype Clone(); void Customize(Dictionary<string, object> userData); string Render();}
// Step 2: Create concrete Prototype classpublic class Document : IDocumentPrototype{ private string templateName; private Dictionary<string, object> userData;
public Document(string templateName) { this.templateName = templateName; this.userData = new Dictionary<string, object>(); // Expensive operations happen only once during prototype creation LoadTemplate(); FetchTemplateData(); FormatTemplate(); Console.WriteLine("⏱️ Creating document prototype (expensive - happens once!)"); }
// Copy constructor for cloning private Document(Document other) { this.templateName = other.templateName; this.userData = new Dictionary<string, object>(other.userData); Console.WriteLine("⚡ Cloning document (fast!)"); }
private void LoadTemplate() { Thread.Sleep(200); // Expensive database query Console.WriteLine("📊 Loading template from database..."); }
private void FetchTemplateData() { Thread.Sleep(150); // Expensive network call Console.WriteLine("🌐 Fetching template data from API..."); }
private void FormatTemplate() { Thread.Sleep(100); // Complex formatting Console.WriteLine("📝 Formatting template..."); }
public IDocumentPrototype Clone() { return new Document(this); }
public void Customize(Dictionary<string, object> userData) { this.userData = new Dictionary<string, object>(userData); Console.WriteLine($"✨ Customized with user data: {userData["name"]}"); }
public string Render() { return $"Document: {templateName} for {userData["name"]}"; }}
// Step 3: Create prototype registrypublic class DocumentPrototypeRegistry{ private Dictionary<string, IDocumentPrototype> prototypes = new Dictionary<string, IDocumentPrototype>();
public void Register(string name, IDocumentPrototype prototype) { prototypes[name] = prototype; Console.WriteLine($"📝 Registered document prototype: {name}"); }
public IDocumentPrototype Get(string name) { if (!prototypes.ContainsKey(name)) { throw new ArgumentException($"Prototype {name} not found"); } return prototypes[name].Clone(); }}
// Step 4: Use the patternclass Program{ static void Main() { // Create prototypes once (expensive) var invoicePrototype = new Document("invoice"); var reportPrototype = new Document("report");
// Register prototypes var registry = new DocumentPrototypeRegistry(); registry.Register("invoice", invoicePrototype); registry.Register("report", reportPrototype);
Console.WriteLine("\n" + new string('=', 50)); Console.WriteLine("Generating documents by cloning (fast!)"); Console.WriteLine(new string('=', 50) + "\n");
// Clone documents (fast!) var doc1 = (Document)registry.Get("invoice"); doc1.Customize(new Dictionary<string, object> { { "name", "John" }, { "amount", 100 } });
var doc2 = (Document)registry.Get("invoice"); doc2.Customize(new Dictionary<string, object> { { "name", "Jane" }, { "amount", 200 } });
var doc3 = (Document)registry.Get("report"); doc3.Customize(new Dictionary<string, object> { { "name", "Bob" }, { "department", "Sales" } });
Console.WriteLine($"\n📄 Document 1: {doc1.Render()}"); Console.WriteLine($"📄 Document 2: {doc2.Render()}"); Console.WriteLine($"📄 Document 3: {doc3.Render()}"); }}package main
import ( "errors" "fmt" "strings" "time")
// Step 1: Define the Prototype interfacetype DocumentPrototype interface { Clone() DocumentPrototype Customize(userData map[string]any) Render() string}
// Step 2: Create concrete Prototype structtype Document struct { templateName string userData map[string]any}
func NewDocument(templateName string) *Document { d := &Document{templateName: templateName, userData: make(map[string]any)} // Expensive operations happen only once during prototype creation d.loadTemplate() d.fetchTemplateData() d.formatTemplate() fmt.Println("⏱️ Creating document prototype (expensive - happens once!)") return d}
func (d *Document) loadTemplate() { time.Sleep(200 * time.Millisecond) // Expensive database query fmt.Println("📊 Loading template from database...")}
func (d *Document) fetchTemplateData() { time.Sleep(150 * time.Millisecond) // Expensive network call fmt.Println("🌐 Fetching template data from API...")}
func (d *Document) formatTemplate() { time.Sleep(100 * time.Millisecond) // Complex formatting fmt.Println("📝 Formatting template...")}
func (d *Document) Clone() DocumentPrototype { fmt.Println("⚡ Cloning document (fast!)") cloned := &Document{templateName: d.templateName, userData: make(map[string]any)} for k, v := range d.userData { cloned.userData[k] = v } return cloned}
func (d *Document) Customize(userData map[string]any) { d.userData = make(map[string]any) for k, v := range userData { d.userData[k] = v } fmt.Printf("✨ Customized with user data: %v\n", userData["name"])}
func (d *Document) Render() string { return fmt.Sprintf("Document: %s for %v", d.templateName, d.userData["name"])}
// Step 3: Prototype registrytype DocumentPrototypeRegistry struct { prototypes map[string]DocumentPrototype}
func NewDocumentPrototypeRegistry() *DocumentPrototypeRegistry { return &DocumentPrototypeRegistry{prototypes: make(map[string]DocumentPrototype)}}
func (r *DocumentPrototypeRegistry) Register(name string, prototype DocumentPrototype) { r.prototypes[name] = prototype fmt.Printf("📝 Registered document prototype: %s\n", name)}
func (r *DocumentPrototypeRegistry) Get(name string) (DocumentPrototype, error) { prototype, ok := r.prototypes[name] if !ok { return nil, errors.New("prototype " + name + " not found") } return prototype.Clone(), nil}
// Step 4: Use the patternfunc main() { // Create prototypes once (expensive) invoicePrototype := NewDocument("invoice") reportPrototype := NewDocument("report")
// Register prototypes registry := NewDocumentPrototypeRegistry() registry.Register("invoice", invoicePrototype) registry.Register("report", reportPrototype)
fmt.Println("\n" + strings.Repeat("=", 50)) fmt.Println("Generating documents by cloning (fast!)") fmt.Println(strings.Repeat("=", 50) + "\n")
// Clone documents (fast!) doc1, _ := registry.Get("invoice") doc1.Customize(map[string]any{"name": "John", "amount": 100})
doc2, _ := registry.Get("invoice") doc2.Customize(map[string]any{"name": "Jane", "amount": 200})
doc3, _ := registry.Get("report") doc3.Customize(map[string]any{"name": "Bob", "department": "Sales"})
fmt.Printf("\n📄 Document 1: %s\n", doc1.Render()) fmt.Printf("📄 Document 2: %s\n", doc2.Render()) fmt.Printf("📄 Document 3: %s\n", doc3.Render())}// The Solution: Prototype Pattern#[derive(Clone)]struct Document { title: String, body: String, tags: Vec<String>,}fn main() { let template = Document { title: "Template".into(), body: "Reusable".into(), tags: vec!["draft".into()], }; let copy = template.clone(); println!("{}", copy.title);}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!class Pizza { toppings: string[];
constructor(toppings: string[]) { this.toppings = toppings; // Array is mutable }
shallowClone(): Pizza { const cloned = new Pizza(this.toppings); // Shallow copy - same array reference return cloned; }}
// Problem: Shared reference!const pizza1 = new Pizza(["cheese", "tomato"]);const pizza2 = pizza1.shallowClone();pizza2.toppings.push("pepperoni"); // Modifies pizza1.toppings too!#include <vector>#include <string>
class Pizza {private: std::vector<std::string>* toppings; // Pointer - shallow copy shares it
public: Pizza(const std::vector<std::string>& toppings) { this->toppings = new std::vector<std::string>(toppings); }
Pizza shallowClone() const { Pizza cloned(*this); // Default copy constructor - shallow copy return cloned; // Shares same toppings pointer }
~Pizza() { delete toppings; }};
// Problem: Shared reference!using System.Collections.Generic;
public class Pizza{ public List<string> Toppings { get; set; }
public Pizza(List<string> toppings) { Toppings = toppings; // List is mutable }
public Pizza ShallowClone() { return new Pizza(this.Toppings); // Shallow copy - same list reference }}
// Problem: Shared reference!// Pizza pizza1 = new Pizza(new List<string> { "cheese", "tomato" });// Pizza pizza2 = pizza1.ShallowClone();// pizza2.Toppings.Add("pepperoni"); // Modifies pizza1.Toppings too!package main
import "fmt"
type Pizza struct { toppings []string // slice is a reference type — shallow copy shares it}
func NewPizza(toppings []string) *Pizza { return &Pizza{toppings: toppings}}
// ShallowClone shares the underlying array — problem!func (p *Pizza) ShallowClone() *Pizza { return &Pizza{toppings: p.toppings} // same underlying array}
func main() { // Problem: Shared reference! pizza1 := NewPizza([]string{"cheese", "tomato"}) pizza2 := pizza1.ShallowClone() pizza2.toppings = append(pizza2.toppings, "pepperoni") // pizza1.toppings may be affected if the underlying array had capacity fmt.Println("pizza1:", pizza1.toppings) fmt.Println("pizza2:", pizza2.toppings)}// 1. Shallow Copy vs Deep Copystruct Document { title: String, body: String, tags: Vec<String>,}fn duplicate(original: &Document) -> Document { Document { title: original.title.clone(), body: original.body.clone(), tags: original.tags.clone(), }}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!class Pizza { toppings: string[];
constructor(toppings: string[]) { this.toppings = toppings; }
deepClone(): Pizza { return new Pizza([...this.toppings]); // Deep copy - new array }}
// Solution: Independent copies!const pizza1 = new Pizza(["cheese", "tomato"]);const pizza2 = pizza1.deepClone();pizza2.toppings.push("pepperoni"); // Only modifies pizza2.toppings#include <vector>#include <string>
class Pizza {private: std::vector<std::string> toppings;
public: Pizza(const std::vector<std::string>& toppings) : toppings(toppings) {}
Pizza deepClone() const { return Pizza(std::vector<std::string>(toppings)); // Deep copy - new vector }};
// Solution: Independent copies!using System.Collections.Generic;using System.Linq;
public class Pizza{ public List<string> Toppings { get; set; }
public Pizza(List<string> toppings) { Toppings = new List<string>(toppings); // Copy list }
public Pizza DeepClone() { return new Pizza(new List<string>(this.Toppings)); // Deep copy }}
// Solution: Independent copies!// Pizza pizza1 = new Pizza(new List<string> { "cheese", "tomato" });// Pizza pizza2 = pizza1.DeepClone();// pizza2.Toppings.Add("pepperoni"); // Only modifies pizza2.Toppingspackage main
import "fmt"
type Pizza struct { toppings []string}
func NewPizza(toppings []string) *Pizza { return &Pizza{toppings: append([]string(nil), toppings...)}}
// DeepClone copies the slice contents — independent copy!func (p *Pizza) DeepClone() *Pizza { return &Pizza{toppings: append([]string(nil), p.toppings...)}}
func main() { // Solution: Independent copies! pizza1 := NewPizza([]string{"cheese", "tomato"}) pizza2 := pizza1.DeepClone() pizza2.toppings = append(pizza2.toppings, "pepperoni") // Only modifies pizza2 fmt.Println("pizza1:", pizza1.toppings) // [cheese tomato] fmt.Println("pizza2:", pizza2.toppings) // [cheese tomato pepperoni]}// 1. Shallow Copy vs Deep Copy#[derive(Clone)]struct Document { title: String, body: String, tags: Vec<String>,}fn main() { let template = Document { title: "Template".into(), body: "Reusable".into(), tags: vec!["draft".into()], }; let copy = template.clone(); println!("{}", copy.title);}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); }}interface Prototype { clone(): Prototype;}
class PrototypeRegistry { private prototypes: Map<string, Prototype> = new Map();
register(name: string, prototype: Prototype): void { this.prototypes.set(name, prototype); }
get(name: string): Prototype { const prototype = this.prototypes.get(name); if (!prototype) { throw new Error(`Prototype ${name} not found`); } return prototype.clone(); }
unregister(name: string): void { this.prototypes.delete(name); }}#include <map>#include <string>#include <memory>
class Prototype {public: virtual ~Prototype() = default; virtual std::unique_ptr<Prototype> clone() = 0;};
class PrototypeRegistry {private: std::map<std::string, std::unique_ptr<Prototype>> prototypes;
public: void registerPrototype(const std::string& name, std::unique_ptr<Prototype> prototype) { prototypes[name] = std::move(prototype); }
std::unique_ptr<Prototype> get(const std::string& name) { auto it = prototypes.find(name); if (it == prototypes.end()) { throw std::invalid_argument("Prototype " + name + " not found"); } return it->second->clone(); }
void unregister(const std::string& name) { prototypes.erase(name); }};using System;using System.Collections.Generic;
public interface IPrototype{ IPrototype Clone();}
public class PrototypeRegistry{ private Dictionary<string, IPrototype> prototypes = new Dictionary<string, IPrototype>();
public void Register(string name, IPrototype prototype) { prototypes[name] = prototype; }
public IPrototype Get(string name) { if (!prototypes.ContainsKey(name)) { throw new ArgumentException($"Prototype {name} not found"); } return prototypes[name].Clone(); }
public void Unregister(string name) { prototypes.Remove(name); }}package main
import "errors"
// Prototype interfacetype Prototype interface { Clone() Prototype}
// PrototypeRegistry manages named prototypestype PrototypeRegistry struct { prototypes map[string]Prototype}
func NewPrototypeRegistry() *PrototypeRegistry { return &PrototypeRegistry{prototypes: make(map[string]Prototype)}}
func (r *PrototypeRegistry) Register(name string, prototype Prototype) { r.prototypes[name] = prototype}
func (r *PrototypeRegistry) Get(name string) (Prototype, error) { prototype, ok := r.prototypes[name] if !ok { return nil, errors.New("prototype " + name + " not found") } return prototype.Clone(), nil}
func (r *PrototypeRegistry) Unregister(name string) { delete(r.prototypes, name)}// 2. Prototype Registry#[derive(Clone)]struct Document { title: String, body: String, tags: Vec<String>,}fn main() { let template = Document { title: "Template".into(), body: "Reusable".into(), tags: vec!["draft".into()], }; let copy = template.clone(); println!("{}", copy.title);}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! }}// ❌ Bad: Shallow copy with mutable objectsclass Pizza { toppings: string[];
constructor(toppings: string[]) { this.toppings = toppings; // Mutable array! }
clone(): Pizza { return new Pizza(this.toppings); // Shallow copy - problem! }}
const pizza1 = new Pizza(["cheese"]);const pizza2 = pizza1.clone();pizza2.toppings.push("pepperoni"); // Also modifies pizza1!
// ✅ Good: Deep copy for mutable objectsclass PizzaGood { toppings: string[];
clone(): PizzaGood { return new PizzaGood([...this.toppings]); // Deep copy - correct! }}#include <vector>#include <string>
// ❌ Bad: Shallow copy with mutable objectsclass Pizza {private: std::vector<std::string>* toppings;
public: Pizza clone() { Pizza cloned; cloned.toppings = this->toppings; // Same pointer - problem! return cloned; }};
// ✅ Good: Deep copy for mutable objectsclass PizzaGood {private: std::vector<std::string> toppings;
public: PizzaGood clone() { PizzaGood cloned; cloned.toppings = std::vector<std::string>(this->toppings); // New vector - correct! return cloned; }};using System.Collections.Generic;
// ❌ Bad: Shallow copy with mutable objectspublic class Pizza{ public List<string> Toppings { get; set; }
public Pizza Clone() { return new Pizza { Toppings = this.Toppings }; // Same list reference - problem! }}
// var pizza1 = new Pizza { Toppings = new List<string> { "cheese" } };// var pizza2 = pizza1.Clone();// pizza2.Toppings.Add("pepperoni"); // Also modifies pizza1!
// ✅ Good: Deep copy for mutable objectspublic class PizzaGood{ public List<string> Toppings { get; set; }
public PizzaGood Clone() { return new PizzaGood { Toppings = new List<string>(this.Toppings) }; // New list - correct! }}package main
import "fmt"
// ❌ Bad: Shallow copy with mutable slicetype PizzaBad struct { toppings []string}
func (p *PizzaBad) Clone() *PizzaBad { return &PizzaBad{toppings: p.toppings} // shares underlying array — problem!}
// ✅ Good: Deep copy for mutable slicestype PizzaGood struct { toppings []string}
func (p *PizzaGood) Clone() *PizzaGood { return &PizzaGood{toppings: append([]string(nil), p.toppings...)} // new slice — correct!}
func main() { pizza1 := &PizzaBad{toppings: []string{"cheese"}} pizza2 := pizza1.Clone() pizza2.toppings = append(pizza2.toppings[:len(pizza2.toppings):len(pizza2.toppings)], "pepperoni") fmt.Println("Bad - pizza1:", pizza1.toppings) // may be affected
good1 := &PizzaGood{toppings: []string{"cheese"}} good2 := good1.Clone() good2.toppings = append(good2.toppings, "pepperoni") fmt.Println("Good - good1:", good1.toppings) // [cheese] — unchanged fmt.Println("Good - good2:", good2.toppings) // [cheese pepperoni]}// Mistake 1: Shallow Copy When Deep Copy is Neededstruct Document { title: String, body: String, tags: Vec<String>,}fn duplicate(original: &Document) -> Document { Document { title: original.title.clone(), body: original.body.clone(), tags: original.tags.clone(), }}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! }}// ❌ Bad: Clone doesn't create independent copyclass Pizza { clone(): Pizza { return this; // Returns same instance - wrong! }}
// ✅ Good: Clone creates new instanceclass PizzaGood { clone(): PizzaGood { const cloned = Object.create(Object.getPrototypeOf(this)); Object.assign(cloned, this); // Creates new instance - correct! return cloned; }}// ❌ Bad: Clone doesn't create independent copyclass Pizza {public: Pizza& clone() { return *this; // Returns same instance - wrong! }};
// ✅ Good: Clone creates new instanceclass PizzaGood {public: PizzaGood clone() const { return PizzaGood(*this); // Creates new instance using copy constructor - correct! }};// ❌ Bad: Clone doesn't create independent copypublic class Pizza{ public Pizza Clone() { return this; // Returns same instance - wrong! }}
// ✅ Good: Clone creates new instancepublic class PizzaGood{ public PizzaGood Clone() { return new PizzaGood(this); // Creates new instance - correct! }}package main
import "fmt"
// ❌ Bad: Clone returns same instancetype PizzaBad struct { size string}
func (p *PizzaBad) Clone() *PizzaBad { return p // Returns same pointer - wrong!}
// ✅ Good: Clone creates new struct valuetype PizzaGood struct { size string}
func (p *PizzaGood) Clone() *PizzaGood { copy := *p // struct copy - creates independent value return ©}
func main() { bad1 := &PizzaBad{size: "large"} bad2 := bad1.Clone() fmt.Println("Same instance?", bad1 == bad2) // true — wrong!
good1 := &PizzaGood{size: "large"} good2 := good1.Clone() fmt.Println("Same instance?", good1 == good2) // false — correct!}// Mistake 2: Not Implementing Clone Properlystruct Document { title: String, body: String, tags: Vec<String>,}fn duplicate(original: &Document) -> Document { Document { title: original.title.clone(), body: original.body.clone(), tags: original.tags.clone(), }}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!// ❌ Bad: Cloning when creation is cheapclass SimpleObject { value: number;
constructor(value: number) { this.value = value; // Simple creation }
clone(): SimpleObject { return JSON.parse(JSON.stringify(this)); // Unnecessary complexity! }}
// ✅ Better: Direct creation for simple objectsconst obj1 = new SimpleObject(10);const obj2 = new SimpleObject(10); // Just create new one!// ❌ Bad: Cloning when creation is cheapclass SimpleObject {private: int value;
public: SimpleObject(int value) : value(value) {} // Simple creation
SimpleObject clone() const { return SimpleObject(this->value); // Unnecessary complexity! }};
// ✅ Better: Direct creation for simple objects// SimpleObject obj1(10);// SimpleObject obj2(10); // Just create new one!// ❌ Bad: Cloning when creation is cheappublic class SimpleObject{ public int Value { get; set; }
public SimpleObject(int value) { Value = value; // Simple creation }
public SimpleObject Clone() { return new SimpleObject(this.Value); // Unnecessary complexity! }}
// ✅ Better: Direct creation for simple objects// SimpleObject obj1 = new SimpleObject(10);// SimpleObject obj2 = new SimpleObject(10); // Just create new one!package main
import "fmt"
// ❌ Bad: Cloning when creation is cheaptype SimpleObjectBad struct { value int}
func (s *SimpleObjectBad) Clone() *SimpleObjectBad { copy := *s return © // Unnecessary complexity!}
// ✅ Better: Direct creation for simple objectstype SimpleObject struct { value int}
func main() { // Unnecessary clone obj1 := &SimpleObjectBad{value: 10} obj2 := obj1.Clone() // Unnecessary! _ = obj2
// Just create new instances directly good1 := &SimpleObject{value: 10} good2 := &SimpleObject{value: 10} // Just create new one! fmt.Println(good1.value, good2.value)}// Mistake 3: Cloning When Creation is Cheapstruct Document { title: String, body: String, tags: Vec<String>,}fn duplicate(original: &Document) -> Document { Document { title: original.title.clone(), body: original.body.clone(), tags: original.tags.clone(), }}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): self._load_template() # Expensive init
def clone(self): return copy.deepcopy(self) # Fast clone!
# Usageprototype = Pizza() # Expensive - happens oncepizza1 = prototype.clone() # Fast!pizza2 = prototype.clone() # Fast!class Pizza implements Cloneable { public Pizza() { loadTemplate(); } // Expensive init
public Pizza clone() { return (Pizza) super.clone(); // Fast clone! }}
// UsagePizza prototype = new Pizza(); // Expensive - happens oncePizza pizza1 = prototype.clone(); // Fast!Pizza pizza2 = prototype.clone(); // Fast!class Pizza { constructor() { this.loadTemplate(); } // Expensive init
clone(): Pizza { return Object.assign(Object.create(Object.getPrototypeOf(this)), this); }}
// Usageconst prototype = new Pizza(); // Expensive - happens onceconst pizza1 = prototype.clone(); // Fast!const pizza2 = prototype.clone(); // Fast!class Pizza {public: Pizza() { loadTemplate(); } // Expensive init
virtual Pizza* clone() { return new Pizza(*this); // Fast clone! }};
// UsagePizza* prototype = new Pizza(); // Expensive - happens oncePizza* pizza1 = prototype->clone(); // Fast!Pizza* pizza2 = prototype->clone(); // Fast!class Pizza : ICloneable { public Pizza() { LoadTemplate(); } // Expensive init
public object Clone() => MemberwiseClone(); // Fast clone!}
// Usagevar prototype = new Pizza(); // Expensive - happens oncevar pizza1 = (Pizza)prototype.Clone(); // Fast!var pizza2 = (Pizza)prototype.Clone(); // Fast!package main
import "fmt"
type Pizza struct { size string}
func NewPizzaPrototype() *Pizza { p := &Pizza{} p.loadTemplate() // Expensive init — happens once return p}
func (p *Pizza) loadTemplate() { fmt.Println("📊 Loading template (expensive)...")}
// Clone creates a new independent copy — fast!func (p *Pizza) Clone() *Pizza { copy := *p return ©}
func main() { prototype := NewPizzaPrototype() // Expensive - happens once pizza1 := prototype.Clone() // Fast! pizza2 := prototype.Clone() // Fast! _ = pizza1 _ = pizza2}// Simple Example#[derive(Clone)]struct Document { title: String, body: String, tags: Vec<String>,}fn main() { let template = Document { title: "Template".into(), body: "Reusable".into(), tags: vec!["draft".into()], }; let copy = template.clone(); println!("{}", copy.title);}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!interface Prototype { Prototype clone(); }
class ConcretePrototype implements Prototype { public Prototype clone() { return (Prototype) super.clone(); }}
// UsagePrototype prototype = new ConcretePrototype();Prototype clone1 = prototype.clone();Prototype clone2 = prototype.clone();interface Prototype { clone(): Prototype; }
class ConcretePrototype implements Prototype { clone(): Prototype { return Object.assign({}, this); }}
// Usageconst prototype = new ConcretePrototype();const clone1 = prototype.clone();const clone2 = prototype.clone();class Prototype {public: virtual Prototype* clone() = 0;};
class ConcretePrototype : public Prototype {public: Prototype* clone() override { return new ConcretePrototype(*this); }};
// UsagePrototype* prototype = new ConcretePrototype();Prototype* clone1 = prototype->clone();Prototype* clone2 = prototype->clone();interface IPrototype { IPrototype Clone(); }
class ConcretePrototype : IPrototype { public IPrototype Clone() => (IPrototype)MemberwiseClone();}
// UsageIPrototype prototype = new ConcretePrototype();var clone1 = prototype.Clone();var clone2 = prototype.Clone();package main
// Prototype interfacetype Prototype interface { Clone() Prototype}
// ConcretePrototype implements Prototypetype ConcretePrototype struct { value string}
func (p *ConcretePrototype) Clone() Prototype { copy := *p return ©}
// Usagefunc main() { var prototype Prototype = &ConcretePrototype{value: "example"} clone1 := prototype.Clone() clone2 := prototype.Clone() _ = clone1 _ = clone2}// Common Pattern Structure#[derive(Clone)]struct Document { title: String, body: String, tags: Vec<String>,}fn main() { let template = Document { title: "Template".into(), body: "Reusable".into(), tags: vec!["draft".into()], }; let copy = template.clone(); println!("{}", copy.title);}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! 🎯