The Prototype Pattern is a creational design pattern that allows you to create new objects by copying existing instances (prototypes) rather than creating them from scratch. This pattern is particularly useful when object creation is expensive or complex, and you want to avoid the overhead of initializing objects repeatedly with the same configuration.
Core Concept
Instead of instantiating objects directly using constructors, the Prototype Pattern involves:
- Creating a prototype object that serves as a template
- Cloning this prototype to create new instances
- Customizing the cloned objects as needed
The pattern decouples the client code from the specific classes of objects it needs to create, promoting flexibility and reducing coupling.
When to Use
The Prototype Pattern is beneficial when:
- Object creation is costly in terms of time or resources
- You need to create many objects with similar configurations
- You want to avoid complex initialization logic
- The system should be independent of how objects are created
- You need to create objects at runtime based on dynamic conditions
Key Components
┌─────────────────┐ ┌─────────────────┐
│ Client │───▶│ Prototype │
│ │ │ (Interface) │
└─────────────────┘ │ + clone() │
└─────────────────┘
▲
│
┌───────┴───────┐
│ │
┌─────────────────┐ ┌─────────────────┐
│ConcretePrototype│ │ConcretePrototype│
│ A │ │ B │
│ + clone() │ │ + clone() │
└─────────────────┘ └─────────────────┘
- Prototype: Interface or abstract class defining the clone method
- ConcretePrototype: Implements the cloning operation
- Client: Creates new objects by asking prototypes to clone themselves
Advantages
- Performance: Reduces object creation overhead when initialization is expensive
- Flexibility: Allows runtime object creation without knowing specific classes
- Simplified Creation: Eliminates the need for complex factory hierarchies
- Dynamic Configuration: Easy to add or remove prototypes at runtime
Disadvantages
- Deep vs Shallow Copy: Managing object references can be complex
- Circular References: Can cause issues during cloning
- Clone Implementation: Requires careful implementation of the clone method
Basic Example
This basic example demonstrates the Prototype Pattern through a document management system. The key elements include:
from abc import ABC, abstractmethod
import copy
from typing import Dict, Any
class Prototype(ABC):
"""
Abstract base class that defines the prototype interface.
All concrete prototypes must implement the clone method.
"""
@abstractmethod
def clone(self) -> 'Prototype':
"""Create and return a copy of the current object."""
pass
class Document(Prototype):
"""
Concrete prototype representing a document with various properties.
This demonstrates cloning of objects with complex state.
"""
def __init__(self, title: str, content: str, author: str) -> None:
self.title = title
self.content = content
self.author = author
self.metadata: Dict[str, Any] = {}
self.created_at = "2024-01-01" # Simplified timestamp
def clone(self) -> 'Document':
"""
Create a deep copy of the document.
This ensures that modifications to the clone don't affect the original.
"""
# Create a new instance with the same basic properties
cloned_doc = Document(self.title, self.content, self.author)
# Deep copy complex attributes to avoid shared references
cloned_doc.metadata = copy.deepcopy(self.metadata)
cloned_doc.created_at = self.created_at
return cloned_doc
def add_metadata(self, key: str, value: Any) -> None:
"""Add metadata to the document."""
self.metadata[key] = value
def __str__(self) -> str:
return (f"Document(title='{self.title}', author='{self.author}', "
f"metadata={self.metadata})")
class PrototypeRegistry:
"""
Registry to manage and provide access to prototype instances.
This centralizes prototype management and makes the pattern more flexible.
"""
def __init__(self) -> None:
self._prototypes: Dict[str, Prototype] = {}
def register_prototype(self, name: str, prototype: Prototype) -> None:
"""Register a prototype with a given name."""
self._prototypes[name] = prototype
def get_prototype(self, name: str) -> Prototype:
"""Get a clone of the registered prototype."""
if name not in self._prototypes:
raise ValueError(f"Prototype '{name}' not found in registry")
return self._prototypes[name].clone()
def list_prototypes(self) -> list[str]:
"""Return a list of all registered prototype names."""
return list(self._prototypes.keys())
# Example usage demonstrating the Prototype Pattern
def main():
# Create a prototype registry
registry = PrototypeRegistry()
# Create original prototype documents
template_report = Document(
title="Monthly Report Template",
content="# Monthly Report\n\n## Summary\n[Add summary here]",
author="System"
)
template_report.add_metadata("type", "report")
template_report.add_metadata("template_version", "1.0")
template_memo = Document(
title="Memo Template",
content="TO: [Recipient]\nFROM: [Sender]\nSUBJECT: [Subject]",
author="System"
)
template_memo.add_metadata("type", "memo")
template_memo.add_metadata("priority", "normal")
# Register prototypes
registry.register_prototype("report_template", template_report)
registry.register_prototype("memo_template", template_memo)
print("Available prototypes:", registry.list_prototypes())
print()
# Create new documents by cloning prototypes
january_report = registry.get_prototype("report_template")
january_report.title = "January 2024 Sales Report"
january_report.author = "Alice Johnson"
january_report.add_metadata("month", "January")
january_report.add_metadata("department", "Sales")
urgent_memo = registry.get_prototype("memo_template")
urgent_memo.title = "Urgent: System Maintenance"
urgent_memo.author = "Bob Smith"
urgent_memo.add_metadata("priority", "urgent")
urgent_memo.add_metadata("deadline", "2024-01-15")
# Demonstrate that clones are independent
print("Original report template:")
print(template_report)
print()
print("Cloned January report:")
print(january_report)
print()
print("Original memo template:")
print(template_memo)
print()
print("Cloned urgent memo:")
print(urgent_memo)
print()
# Show that modifications to clones don't affect originals
print("Template metadata unchanged after cloning:")
print(f"Report template priority: {template_report.metadata.get('priority', 'Not set')}")
print(f"Memo template month: {template_memo.metadata.get('month', 'Not set')}")
if __name__ == "__main__":
main()
Advanced Example
from abc import ABC, abstractmethod
import copy
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
@dataclass
class Equipment:
"""Represents a piece of equipment that a character can have."""
name: str
damage: int = 0
defense: int = 0
magic_power: int = 0
@dataclass
class Stats:
"""Character statistics that can be modified."""
health: int = 100
mana: int = 50
strength: int = 10
intelligence: int = 10
agility: int = 10
class GameCharacter(ABC):
"""
Abstract prototype for game characters.
Demonstrates prototype pattern in game development context.
"""
def __init__(self, name: str, character_class: str) -> None:
self.name = name
self.character_class = character_class
self.level = 1
self.stats = Stats()
self.equipment: List[Equipment] = []
self.skills: List[str] = []
self.experience = 0
@abstractmethod
def clone(self) -> 'GameCharacter':
"""Create a copy of the character."""
pass
@abstractmethod
def get_special_abilities(self) -> List[str]:
"""Get character-specific special abilities."""
pass
def add_equipment(self, equipment: Equipment) -> None:
"""Add equipment to the character."""
self.equipment.append(equipment)
def add_skill(self, skill: str) -> None:
"""Add a skill to the character."""
if skill not in self.skills:
self.skills.append(skill)
def calculate_total_stats(self) -> Stats:
"""Calculate total stats including equipment bonuses."""
total_stats = copy.deepcopy(self.stats)
for equipment in self.equipment:
total_stats.strength += equipment.damage
total_stats.intelligence += equipment.magic_power
# Simplified calculation for demonstration
return total_stats
def __str__(self) -> str:
total_stats = self.calculate_total_stats()
return (f"{self.character_class} '{self.name}' (Level {self.level})\n"
f" Stats: HP={total_stats.health}, MP={total_stats.mana}, "
f"STR={total_stats.strength}, INT={total_stats.intelligence}\n"
f" Equipment: {len(self.equipment)} items\n"
f" Skills: {self.skills}")
class Warrior(GameCharacter):
"""Concrete prototype for warrior characters."""
def __init__(self, name: str) -> None:
super().__init__(name, "Warrior")
# Warriors start with higher health and strength
self.stats.health = 150
self.stats.strength = 15
self.stats.agility = 8
self.add_skill("Sword Fighting")
self.add_skill("Shield Block")
def clone(self) -> 'Warrior':
"""Create a deep copy of the warrior."""
# Create new warrior instance
cloned_warrior = Warrior(self.name)
# Copy all attributes
cloned_warrior.level = self.level
cloned_warrior.stats = copy.deepcopy(self.stats)
cloned_warrior.equipment = copy.deepcopy(self.equipment)
cloned_warrior.skills = copy.deepcopy(self.skills)
cloned_warrior.experience = self.experience
return cloned_warrior
def get_special_abilities(self) -> List[str]:
return ["Berserker Rage", "Defensive Stance", "Weapon Mastery"]
class Mage(GameCharacter):
"""Concrete prototype for mage characters."""
def __init__(self, name: str) -> None:
super().__init__(name, "Mage")
# Mages start with higher mana and intelligence
self.stats.health = 80
self.stats.mana = 120
self.stats.intelligence = 18
self.stats.strength = 6
self.add_skill("Fireball")
self.add_skill("Healing")
self.add_skill("Teleport")
def clone(self) -> 'Mage':
"""Create a deep copy of the mage."""
cloned_mage = Mage(self.name)
# Copy all attributes
cloned_mage.level = self.level
cloned_mage.stats = copy.deepcopy(self.stats)
cloned_mage.equipment = copy.deepcopy(self.equipment)
cloned_mage.skills = copy.deepcopy(self.skills)
cloned_mage.experience = self.experience
return cloned_mage
def get_special_abilities(self) -> List[str]:
return ["Arcane Blast", "Mana Shield", "Spell Amplification"]
class CharacterFactory:
"""
Factory that uses prototypes to create characters efficiently.
Demonstrates how prototype pattern integrates with factory pattern.
"""
def __init__(self) -> None:
self._prototypes: Dict[str, GameCharacter] = {}
self._setup_default_prototypes()
def _setup_default_prototypes(self) -> None:
"""Set up default character prototypes with basic equipment."""
# Create warrior prototype
warrior_proto = Warrior("Warrior Template")
warrior_proto.add_equipment(Equipment("Iron Sword", damage=5))
warrior_proto.add_equipment(Equipment("Leather Armor", defense=3))
# Create mage prototype
mage_proto = Mage("Mage Template")
mage_proto.add_equipment(Equipment("Wooden Staff", magic_power=4))
mage_proto.add_equipment(Equipment("Cloth Robe", defense=1, magic_power=2))
self._prototypes["warrior"] = warrior_proto
self._prototypes["mage"] = mage_proto
def register_prototype(self, character_type: str, prototype: GameCharacter) -> None:
"""Register a new prototype."""
self._prototypes[character_type] = prototype
def create_character(self, character_type: str, name: str) -> Optional[GameCharacter]:
"""Create a new character by cloning a prototype."""
if character_type not in self._prototypes:
return None
# Clone the prototype
new_character = self._prototypes[character_type].clone()
# Customize with the new name
new_character.name = name
return new_character
def get_available_types(self) -> List[str]:
"""Get list of available character types."""
return list(self._prototypes.keys())
def customize_prototype(self, character_type: str,
equipment_list: List[Equipment] = None,
skills_list: List[str] = None) -> bool:
"""Customize an existing prototype with additional equipment/skills."""
if character_type not in self._prototypes:
return False
prototype = self._prototypes[character_type]
if equipment_list:
for equipment in equipment_list:
prototype.add_equipment(equipment)
if skills_list:
for skill in skills_list:
prototype.add_skill(skill)
return True
# Demonstration of the advanced prototype pattern
def main():
# Create character factory
factory = CharacterFactory()
print("Available character types:", factory.get_available_types())
print()
# Create characters using prototypes
hero1 = factory.create_character("warrior", "Conan the Barbarian")
hero2 = factory.create_character("mage", "Gandalf the Wise")
hero3 = factory.create_character("warrior", "Aragorn the Ranger")
if not all([hero1, hero2, hero3]):
print("Error creating characters")
return
print("=== Original Characters ===")
print(hero1)
print()
print(hero2)
print()
print(hero3)
print()
# Customize characters after creation
hero1.level = 5
hero1.experience = 1250
hero1.add_equipment(Equipment("Dragon Slayer Sword", damage=15))
hero1.add_skill("Dragon Slaying")
hero2.level = 7
hero2.experience = 2100
hero2.add_equipment(Equipment("Staff of Power", magic_power=12))
hero2.add_skill("Lightning Bolt")
print("=== Customized Characters ===")
print(hero1)
print("Special Abilities:", hero1.get_special_abilities())
print()
print(hero2)
print("Special Abilities:", hero2.get_special_abilities())
print()
# Demonstrate prototype customization
print("=== Customizing Prototypes ===")
# Add legendary equipment to warrior prototype
legendary_equipment = [
Equipment("Legendary Armor", defense=10, damage=3),
Equipment("Ring of Strength", damage=5)
]
factory.customize_prototype("warrior",
equipment_list=legendary_equipment,
skills_list=["Legendary Strike"])
# Create new warrior with enhanced prototype
legendary_warrior = factory.create_character("warrior", "Arthur the Legend")
print("New warrior created with enhanced prototype:")
print(legendary_warrior)
print()
# Show that original characters are unaffected
print("=== Original Characters Unchanged ===")
hero4 = factory.create_character("warrior", "Basic Warrior")
print("Another warrior shows prototype changes:")
print(hero4)
if __name__ == "__main__":
main()
This advanced example demonstrates the Prototype Pattern in a game development context, showcasing several important concepts:
Complex Object Hierarchy: The example uses GameCharacter as an abstract base class with concrete implementations (Warrior, Mage) that have different characteristics and behaviors.
Deep Cloning of Complex State: Each character has multiple complex attributes (stats, equipment, skills) that require proper deep copying to ensure clones are independent. The clone methods carefully duplicate all nested objects.
Integration with Factory Pattern: The CharacterFactory combines the Prototype and Factory patterns, using prototypes internally to create characters efficiently while providing a clean factory interface.
Runtime Prototype Customization: The factory allows customization of prototypes at runtime, demonstrating how the pattern supports dynamic behavior modification.
Performance Benefits: In a real game, creating characters from scratch might involve complex calculations, database lookups, or resource loading. The prototype pattern allows you to do this expensive work once and then quickly clone characters as needed.
The example shows how the Prototype Pattern is particularly useful in scenarios where:
- Object initialization is expensive (loading character stats, equipment, etc.)
- You need many similar objects with slight variations
- You want to avoid tight coupling between client code and specific character classes
- You need to support runtime configuration of object templates