Skip to content

Prototype Pattern

Category: Creational Pattern

Overview

Create new objects by cloning existing instances rather than creating new ones from scratch. This pattern is useful when object creation is expensive or when you want to avoid the complexity of instantiating an object directly, leveraging Python's built-in copy module for shallow and deep copying.

Usage Guidelines

Use when:

  • Creating new objects is more expensive than cloning existing ones
  • Objects require complex setup that can be reused
  • Types to create are determined at runtime
  • Need copies of objects in specific states

Avoid when:

  • Creating new objects is straightforward and cheap
  • Complexity of managing object references outweighs benefits
  • Objects contain circular references that complicate cloning
  • Objects are immutable and can be safely shared

Implementation

from __future__ import annotations
import copy
from typing import Any

class Prototype:
    """Abstract base class for prototypes.

    Defines the interface for cloning objects.
    """

    def clone(self) -> Prototype:
        """Create a shallow copy of the object.

        Returns:
            A shallow copy of the prototype.
        """
        return copy.copy(self)

    def deep_clone(self) -> Prototype:
        """Create a deep copy of the object.

        Returns:
            A deep copy of the prototype.
        """
        return copy.deepcopy(self)

class Document(Prototype):
    """Represents a document that can be cloned.

    This demonstrates the prototype pattern with both shallow and deep copying.
    """

    def __init__(self, title: str, font: str, font_size: int) -> None:
        """Initialize a document.

        Args:
            title: The document title.
            font: The font name.
            font_size: The font size.
        """
        self.title = title
        self.font = font
        self.font_size = font_size
        self.sections: list[str] = []
        self.metadata: dict[str, Any] = {}

    def add_section(self, section: str) -> None:
        """Add a section to the document.

        Args:
            section: The section name or content.
        """
        self.sections.append(section)

    def set_metadata(self, key: str, value: Any) -> None:
        """Set document metadata.

        Args:
            key: The metadata key.
            value: The metadata value.
        """
        self.metadata[key] = value

    def get_info(self) -> str:
        """Get document information.

        Returns:
            A string describing the document.
        """
        return (f"Document: {self.title}, "
                f"Font: {self.font} {self.font_size}pt, "
                f"Sections: {len(self.sections)}")

Usage

# Create original document
original = Document("Report", "Arial", 12)
original.add_section("Introduction")
original.add_section("Methodology")
original.set_metadata("author", "John Doe")

# Shallow copy - shares mutable references
shallow_copy = original.clone()
shallow_copy.title = "Modified Report"
shallow_copy.add_section("Results")  # Affects original too!

# Deep copy - independent copy
deep_copy = original.deep_clone()
deep_copy.title = "Independent Report"
deep_copy.add_section("Conclusion")  # Does NOT affect original

print(f"Original sections: {len(original.sections)}")  # 3
print(f"Deep copy sections: {len(deep_copy.sections)}")  # 4

Trade-offs

Benefits:

  1. Cloning can be faster than creating objects from scratch
  2. Add or remove prototypes at runtime for flexibility
  3. Avoid factory hierarchies for product variants
  4. Clone objects in specific configured states

Drawbacks:

  1. Managing shallow vs deep copy semantics can be tricky
  2. Objects with circular references are hard to clone
  3. Implementing proper cloning can be complex
  4. Cloned objects may need additional initialization

Real-World Examples

  • Document templates with pre-configured settings
  • Game objects with preset configurations (enemies, weapons, items)
  • Graphics editors cloning shapes or design elements
  • Test fixtures cloning test data objects
  • Abstract Factory
  • Composite
  • Decorator
  • Memento
  • Singleton

API Reference

design_patterns.creational.prototype

Prototype Pattern Module

The Prototype pattern creates new objects by cloning existing instances rather than creating new ones from scratch. This is useful when object creation is expensive or when you want to avoid the complexity of instantiating an object directly.

Python provides built-in support for prototyping through the copy module, which offers both shallow and deep copying mechanisms.

Example

Cloning a document with its formatting:

original = Document("Report", "Arial", 12)
original.add_section("Introduction")

# Shallow copy - shared references
copy1 = original.clone()

# Deep copy - independent copy
copy2 = original.deep_clone()

Circle

Bases: Shape

Represents a circle shape.

Source code in src/design_patterns/creational/prototype.py
class Circle(Shape):
    """Represents a circle shape."""

    def __init__(self, x: int, y: int, color: str, radius: int) -> None:
        """Initialize a circle.

        Args:
            x: X coordinate of center.
            y: Y coordinate of center.
            color: Circle color.
            radius: Circle radius.
        """
        super().__init__(x, y, color)
        self.radius = radius

    def __repr__(self) -> str:
        """Return string representation of the circle.

        Returns:
            String representation.
        """
        return f"Circle(x={self.x}, y={self.y}, color='{self.color}', radius={self.radius})"

__init__(x, y, color, radius)

Initialize a circle.

Parameters:

Name Type Description Default
x int

X coordinate of center.

required
y int

Y coordinate of center.

required
color str

Circle color.

required
radius int

Circle radius.

required
Source code in src/design_patterns/creational/prototype.py
def __init__(self, x: int, y: int, color: str, radius: int) -> None:
    """Initialize a circle.

    Args:
        x: X coordinate of center.
        y: Y coordinate of center.
        color: Circle color.
        radius: Circle radius.
    """
    super().__init__(x, y, color)
    self.radius = radius

__repr__()

Return string representation of the circle.

Returns:

Type Description
str

String representation.

Source code in src/design_patterns/creational/prototype.py
def __repr__(self) -> str:
    """Return string representation of the circle.

    Returns:
        String representation.
    """
    return f"Circle(x={self.x}, y={self.y}, color='{self.color}', radius={self.radius})"

Document

Bases: Prototype

Represents a document that can be cloned.

This demonstrates the prototype pattern with both shallow and deep copying.

Source code in src/design_patterns/creational/prototype.py
class Document(Prototype):
    """Represents a document that can be cloned.

    This demonstrates the prototype pattern with both shallow and deep copying.
    """

    def __init__(self, title: str, font: str, font_size: int) -> None:
        """Initialize a document.

        Args:
            title: The document title.
            font: The font name.
            font_size: The font size.
        """
        self.title = title
        self.font = font
        self.font_size = font_size
        self.sections: list[str] = []
        self.metadata: dict[str, Any] = {}

    def add_section(self, section: str) -> None:
        """Add a section to the document.

        Args:
            section: The section name or content.
        """
        self.sections.append(section)

    def set_metadata(self, key: str, value: Any) -> None:
        """Set document metadata.

        Args:
            key: The metadata key.
            value: The metadata value.
        """
        self.metadata[key] = value

    def get_info(self) -> str:
        """Get document information.

        Returns:
            A string describing the document.
        """
        return (f"Document: {self.title}, "
                f"Font: {self.font} {self.font_size}pt, "
                f"Sections: {len(self.sections)}")

__init__(title, font, font_size)

Initialize a document.

Parameters:

Name Type Description Default
title str

The document title.

required
font str

The font name.

required
font_size int

The font size.

required
Source code in src/design_patterns/creational/prototype.py
def __init__(self, title: str, font: str, font_size: int) -> None:
    """Initialize a document.

    Args:
        title: The document title.
        font: The font name.
        font_size: The font size.
    """
    self.title = title
    self.font = font
    self.font_size = font_size
    self.sections: list[str] = []
    self.metadata: dict[str, Any] = {}

add_section(section)

Add a section to the document.

Parameters:

Name Type Description Default
section str

The section name or content.

required
Source code in src/design_patterns/creational/prototype.py
def add_section(self, section: str) -> None:
    """Add a section to the document.

    Args:
        section: The section name or content.
    """
    self.sections.append(section)

get_info()

Get document information.

Returns:

Type Description
str

A string describing the document.

Source code in src/design_patterns/creational/prototype.py
def get_info(self) -> str:
    """Get document information.

    Returns:
        A string describing the document.
    """
    return (f"Document: {self.title}, "
            f"Font: {self.font} {self.font_size}pt, "
            f"Sections: {len(self.sections)}")

set_metadata(key, value)

Set document metadata.

Parameters:

Name Type Description Default
key str

The metadata key.

required
value Any

The metadata value.

required
Source code in src/design_patterns/creational/prototype.py
def set_metadata(self, key: str, value: Any) -> None:
    """Set document metadata.

    Args:
        key: The metadata key.
        value: The metadata value.
    """
    self.metadata[key] = value

Prototype

Abstract base class for prototypes.

Defines the interface for cloning objects.

Source code in src/design_patterns/creational/prototype.py
class Prototype:
    """Abstract base class for prototypes.

    Defines the interface for cloning objects.
    """

    def clone(self) -> Prototype:
        """Create a shallow copy of the object.

        Returns:
            A shallow copy of the prototype.
        """
        return copy.copy(self)

    def deep_clone(self) -> Prototype:
        """Create a deep copy of the object.

        Returns:
            A deep copy of the prototype.
        """
        return copy.deepcopy(self)

clone()

Create a shallow copy of the object.

Returns:

Type Description
Prototype

A shallow copy of the prototype.

Source code in src/design_patterns/creational/prototype.py
def clone(self) -> Prototype:
    """Create a shallow copy of the object.

    Returns:
        A shallow copy of the prototype.
    """
    return copy.copy(self)

deep_clone()

Create a deep copy of the object.

Returns:

Type Description
Prototype

A deep copy of the prototype.

Source code in src/design_patterns/creational/prototype.py
def deep_clone(self) -> Prototype:
    """Create a deep copy of the object.

    Returns:
        A deep copy of the prototype.
    """
    return copy.deepcopy(self)

PrototypeRegistry

Registry for managing prototype instances.

This allows storing and retrieving prototype objects by name, which can then be cloned to create new instances.

Source code in src/design_patterns/creational/prototype.py
class PrototypeRegistry:
    """Registry for managing prototype instances.

    This allows storing and retrieving prototype objects by name,
    which can then be cloned to create new instances.
    """

    def __init__(self) -> None:
        """Initialize an empty registry."""
        self._prototypes: dict[str, Prototype] = {}

    def register(self, name: str, prototype: Prototype) -> None:
        """Register a prototype with a given name.

        Args:
            name: The name to register the prototype under.
            prototype: The prototype object to register.
        """
        self._prototypes[name] = prototype

    def unregister(self, name: str) -> None:
        """Unregister a prototype.

        Args:
            name: The name of the prototype to unregister.
        """
        if name in self._prototypes:
            del self._prototypes[name]

    def clone(self, name: str) -> Prototype:
        """Clone a registered prototype.

        Args:
            name: The name of the prototype to clone.

        Returns:
            A shallow copy of the registered prototype.

        Raises:
            KeyError: If the prototype name is not registered.
        """
        if name not in self._prototypes:
            raise KeyError(f"Prototype '{name}' not found in registry")
        return self._prototypes[name].clone()

    def deep_clone(self, name: str) -> Prototype:
        """Deep clone a registered prototype.

        Args:
            name: The name of the prototype to clone.

        Returns:
            A deep copy of the registered prototype.

        Raises:
            KeyError: If the prototype name is not registered.
        """
        if name not in self._prototypes:
            raise KeyError(f"Prototype '{name}' not found in registry")
        return self._prototypes[name].deep_clone()

__init__()

Initialize an empty registry.

Source code in src/design_patterns/creational/prototype.py
def __init__(self) -> None:
    """Initialize an empty registry."""
    self._prototypes: dict[str, Prototype] = {}

clone(name)

Clone a registered prototype.

Parameters:

Name Type Description Default
name str

The name of the prototype to clone.

required

Returns:

Type Description
Prototype

A shallow copy of the registered prototype.

Raises:

Type Description
KeyError

If the prototype name is not registered.

Source code in src/design_patterns/creational/prototype.py
def clone(self, name: str) -> Prototype:
    """Clone a registered prototype.

    Args:
        name: The name of the prototype to clone.

    Returns:
        A shallow copy of the registered prototype.

    Raises:
        KeyError: If the prototype name is not registered.
    """
    if name not in self._prototypes:
        raise KeyError(f"Prototype '{name}' not found in registry")
    return self._prototypes[name].clone()

deep_clone(name)

Deep clone a registered prototype.

Parameters:

Name Type Description Default
name str

The name of the prototype to clone.

required

Returns:

Type Description
Prototype

A deep copy of the registered prototype.

Raises:

Type Description
KeyError

If the prototype name is not registered.

Source code in src/design_patterns/creational/prototype.py
def deep_clone(self, name: str) -> Prototype:
    """Deep clone a registered prototype.

    Args:
        name: The name of the prototype to clone.

    Returns:
        A deep copy of the registered prototype.

    Raises:
        KeyError: If the prototype name is not registered.
    """
    if name not in self._prototypes:
        raise KeyError(f"Prototype '{name}' not found in registry")
    return self._prototypes[name].deep_clone()

register(name, prototype)

Register a prototype with a given name.

Parameters:

Name Type Description Default
name str

The name to register the prototype under.

required
prototype Prototype

The prototype object to register.

required
Source code in src/design_patterns/creational/prototype.py
def register(self, name: str, prototype: Prototype) -> None:
    """Register a prototype with a given name.

    Args:
        name: The name to register the prototype under.
        prototype: The prototype object to register.
    """
    self._prototypes[name] = prototype

unregister(name)

Unregister a prototype.

Parameters:

Name Type Description Default
name str

The name of the prototype to unregister.

required
Source code in src/design_patterns/creational/prototype.py
def unregister(self, name: str) -> None:
    """Unregister a prototype.

    Args:
        name: The name of the prototype to unregister.
    """
    if name in self._prototypes:
        del self._prototypes[name]

Rectangle

Bases: Shape

Represents a rectangle shape.

Source code in src/design_patterns/creational/prototype.py
class Rectangle(Shape):
    """Represents a rectangle shape."""

    def __init__(self, x: int, y: int, color: str, width: int, height: int) -> None:
        """Initialize a rectangle.

        Args:
            x: X coordinate of top-left corner.
            y: Y coordinate of top-left corner.
            color: Rectangle color.
            width: Rectangle width.
            height: Rectangle height.
        """
        super().__init__(x, y, color)
        self.width = width
        self.height = height

    def __repr__(self) -> str:
        """Return string representation of the rectangle.

        Returns:
            String representation.
        """
        return (f"Rectangle(x={self.x}, y={self.y}, color='{self.color}', "
                f"width={self.width}, height={self.height})")

__init__(x, y, color, width, height)

Initialize a rectangle.

Parameters:

Name Type Description Default
x int

X coordinate of top-left corner.

required
y int

Y coordinate of top-left corner.

required
color str

Rectangle color.

required
width int

Rectangle width.

required
height int

Rectangle height.

required
Source code in src/design_patterns/creational/prototype.py
def __init__(self, x: int, y: int, color: str, width: int, height: int) -> None:
    """Initialize a rectangle.

    Args:
        x: X coordinate of top-left corner.
        y: Y coordinate of top-left corner.
        color: Rectangle color.
        width: Rectangle width.
        height: Rectangle height.
    """
    super().__init__(x, y, color)
    self.width = width
    self.height = height

__repr__()

Return string representation of the rectangle.

Returns:

Type Description
str

String representation.

Source code in src/design_patterns/creational/prototype.py
def __repr__(self) -> str:
    """Return string representation of the rectangle.

    Returns:
        String representation.
    """
    return (f"Rectangle(x={self.x}, y={self.y}, color='{self.color}', "
            f"width={self.width}, height={self.height})")

Shape

Bases: Prototype

Represents a geometric shape that can be cloned.

This demonstrates cloning with position and style attributes.

Source code in src/design_patterns/creational/prototype.py
class Shape(Prototype):
    """Represents a geometric shape that can be cloned.

    This demonstrates cloning with position and style attributes.
    """

    def __init__(self, x: int, y: int, color: str) -> None:
        """Initialize a shape.

        Args:
            x: X coordinate.
            y: Y coordinate.
            color: Shape color.
        """
        self.x = x
        self.y = y
        self.color = color

    def move(self, dx: int, dy: int) -> None:
        """Move the shape by the given deltas.

        Args:
            dx: Change in x coordinate.
            dy: Change in y coordinate.
        """
        self.x += dx
        self.y += dy

    def __repr__(self) -> str:
        """Return string representation of the shape.

        Returns:
            String representation.
        """
        return f"{self.__class__.__name__}(x={self.x}, y={self.y}, color='{self.color}')"

__init__(x, y, color)

Initialize a shape.

Parameters:

Name Type Description Default
x int

X coordinate.

required
y int

Y coordinate.

required
color str

Shape color.

required
Source code in src/design_patterns/creational/prototype.py
def __init__(self, x: int, y: int, color: str) -> None:
    """Initialize a shape.

    Args:
        x: X coordinate.
        y: Y coordinate.
        color: Shape color.
    """
    self.x = x
    self.y = y
    self.color = color

__repr__()

Return string representation of the shape.

Returns:

Type Description
str

String representation.

Source code in src/design_patterns/creational/prototype.py
def __repr__(self) -> str:
    """Return string representation of the shape.

    Returns:
        String representation.
    """
    return f"{self.__class__.__name__}(x={self.x}, y={self.y}, color='{self.color}')"

move(dx, dy)

Move the shape by the given deltas.

Parameters:

Name Type Description Default
dx int

Change in x coordinate.

required
dy int

Change in y coordinate.

required
Source code in src/design_patterns/creational/prototype.py
def move(self, dx: int, dy: int) -> None:
    """Move the shape by the given deltas.

    Args:
        dx: Change in x coordinate.
        dy: Change in y coordinate.
    """
    self.x += dx
    self.y += dy