Prototype

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

Track your progress

Mark this subtopic as completed when you finish reading.