Prototype Pattern

Introduction

The Prototype pattern creates new objects by cloning an existing instance rather than building one from scratch. When object creation is expensive — involving database queries, file I/O, or complex initialization — copying a pre-configured prototype is significantly faster. This pattern gives you a way to produce new instances with a known-good starting state.

The Problem

Imagine a document management system where users create reports from templates. Each template has a predefined layout, styling, headers, footers, and metadata. Constructing a new document from scratch every time means repeating the same expensive setup — loading fonts, parsing layout rules, and applying styling. Worse, if users frequently create variations of the same template, you are doing redundant work over and over.

The Solution

The Prototype pattern maintains a registry of pre-built template objects. When a user needs a new document, the system clones the appropriate prototype and lets the user customize only what is different. The clone operation copies the full internal state, so the new document starts fully configured and ready for editing — without repeating the original setup cost.

Key Principle

Create by copying, not constructing. The prototype encapsulates the knowledge of how to replicate itself, so clients never need to know the details of initialization. This is especially powerful when the class hierarchy is complex or the exact type is determined at runtime.

Java Example

This example implements a document template system where reports are cloned from pre-built prototypes.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// Prototype interface
public interface DocumentPrototype {
    DocumentPrototype cloneDocument();
}

// Concrete prototype
public class ReportDocument implements DocumentPrototype {
    private String title;
    private String templateType;
    private List<String> sections;
    private Map<String, String> metadata;
    private String headerText;
    private String footerText;
    private String fontFamily;
    private int fontSize;

    public ReportDocument() {
        this.sections = new ArrayList<>();
        this.metadata = new HashMap<>();
    }

    // Deep copy constructor
    private ReportDocument(ReportDocument source) {
        this.title = source.title;
        this.templateType = source.templateType;
        this.sections = new ArrayList<>(source.sections);
        this.metadata = new HashMap<>(source.metadata);
        this.headerText = source.headerText;
        this.footerText = source.footerText;
        this.fontFamily = source.fontFamily;
        this.fontSize = source.fontSize;
    }

    @Override
    public DocumentPrototype cloneDocument() {
        return new ReportDocument(this);
    }

    // Setters for configuration
    public void setTitle(String title) { this.title = title; }
    public void setTemplateType(String type) { this.templateType = type; }
    public void addSection(String section) { this.sections.add(section); }
    public void setMetadata(String key, String value) { this.metadata.put(key, value); }
    public void setHeaderText(String text) { this.headerText = text; }
    public void setFooterText(String text) { this.footerText = text; }
    public void setFont(String family, int size) {
        this.fontFamily = family;
        this.fontSize = size;
    }

    @Override
    public String toString() {
        return "ReportDocument{" +
            "title='" + title + "'" +
            ", type='" + templateType + "'" +
            ", sections=" + sections.size() +
            ", font=" + fontFamily + " " + fontSize + "pt" +
            "}";
    }
}

// Template registry
public class DocumentTemplateRegistry {
    private final Map<String, DocumentPrototype> templates = new HashMap<>();

    public void registerTemplate(String name, DocumentPrototype prototype) {
        templates.put(name, prototype);
    }

    public DocumentPrototype createFromTemplate(String name) {
        DocumentPrototype prototype = templates.get(name);
        if (prototype == null) {
            throw new IllegalArgumentException("Unknown template: " + name);
        }
        return prototype.cloneDocument();
    }
}

// Usage
public class Application {
    public static void main(String[] args) {
        // Build expensive prototypes once
        ReportDocument financialTemplate = new ReportDocument();
        financialTemplate.setTitle("Quarterly Financial Report");
        financialTemplate.setTemplateType("financial");
        financialTemplate.setFont("Arial", 11);
        financialTemplate.setHeaderText("CONFIDENTIAL - Financial Report");
        financialTemplate.setFooterText("Generated by Report System v2.0");
        financialTemplate.addSection("Executive Summary");
        financialTemplate.addSection("Revenue Analysis");
        financialTemplate.addSection("Expense Breakdown");
        financialTemplate.addSection("Projections");
        financialTemplate.setMetadata("department", "Finance");
        financialTemplate.setMetadata("classification", "confidential");

        ReportDocument techTemplate = new ReportDocument();
        techTemplate.setTitle("Technical Review");
        techTemplate.setTemplateType("technical");
        techTemplate.setFont("Courier New", 10);
        techTemplate.setHeaderText("Engineering - Technical Review");
        techTemplate.setFooterText("Internal Use Only");
        techTemplate.addSection("Architecture Overview");
        techTemplate.addSection("Performance Metrics");
        techTemplate.addSection("Risk Assessment");
        techTemplate.setMetadata("department", "Engineering");

        // Register prototypes
        DocumentTemplateRegistry registry = new DocumentTemplateRegistry();
        registry.registerTemplate("financial", financialTemplate);
        registry.registerTemplate("technical", techTemplate);

        // Clone and customize
        ReportDocument q1Report = (ReportDocument) registry.createFromTemplate("financial");
        q1Report.setTitle("Q1 2025 Financial Report");
        System.out.println(q1Report);

        ReportDocument q2Report = (ReportDocument) registry.createFromTemplate("financial");
        q2Report.setTitle("Q2 2025 Financial Report");
        System.out.println(q2Report);

        ReportDocument apiReview = (ReportDocument) registry.createFromTemplate("technical");
        apiReview.setTitle("API Gateway Technical Review");
        System.out.println(apiReview);
    }
}

Python Example

The same document template system in Python, using the copy module for deep cloning.

import copy
from typing import Optional


class ReportDocument:
    def __init__(self):
        self.title: str = ""
        self.template_type: str = ""
        self.sections: list[str] = []
        self.metadata: dict[str, str] = {}
        self.header_text: str = ""
        self.footer_text: str = ""
        self.font_family: str = "Arial"
        self.font_size: int = 12

    def clone(self) -> "ReportDocument":
        """Deep copy this document to create an independent clone."""
        return copy.deepcopy(self)

    def __str__(self):
        return (
            f"ReportDocument(title='{self.title}'"
            f", type='{self.template_type}'"
            f", sections={len(self.sections)}"
            f", font={self.font_family} {self.font_size}pt)"
        )


class DocumentTemplateRegistry:
    def __init__(self):
        self._templates: dict[str, ReportDocument] = {}

    def register(self, name: str, prototype: ReportDocument):
        self._templates[name] = prototype

    def create_from_template(self, name: str) -> ReportDocument:
        prototype = self._templates.get(name)
        if prototype is None:
            raise ValueError(f"Unknown template: {name}")
        return prototype.clone()


# Usage
if __name__ == "__main__":
    # Build expensive prototypes once
    financial_template = ReportDocument()
    financial_template.title = "Quarterly Financial Report"
    financial_template.template_type = "financial"
    financial_template.font_family = "Arial"
    financial_template.font_size = 11
    financial_template.header_text = "CONFIDENTIAL - Financial Report"
    financial_template.footer_text = "Generated by Report System v2.0"
    financial_template.sections = [
        "Executive Summary",
        "Revenue Analysis",
        "Expense Breakdown",
        "Projections",
    ]
    financial_template.metadata = {
        "department": "Finance",
        "classification": "confidential",
    }

    tech_template = ReportDocument()
    tech_template.title = "Technical Review"
    tech_template.template_type = "technical"
    tech_template.font_family = "Courier New"
    tech_template.font_size = 10
    tech_template.header_text = "Engineering - Technical Review"
    tech_template.footer_text = "Internal Use Only"
    tech_template.sections = [
        "Architecture Overview",
        "Performance Metrics",
        "Risk Assessment",
    ]
    tech_template.metadata = {"department": "Engineering"}

    # Register prototypes
    registry = DocumentTemplateRegistry()
    registry.register("financial", financial_template)
    registry.register("technical", tech_template)

    # Clone and customize
    q1_report = registry.create_from_template("financial")
    q1_report.title = "Q1 2025 Financial Report"
    print(q1_report)

    q2_report = registry.create_from_template("financial")
    q2_report.title = "Q2 2025 Financial Report"
    print(q2_report)

    api_review = registry.create_from_template("technical")
    api_review.title = "API Gateway Technical Review"
    print(api_review)

    # Prove independence — original is unchanged
    print(f"Original title: {financial_template.title}")
    print(f"Clone title: {q1_report.title}")

When to Use

  • Expensive initialization — when constructing an object requires costly operations like database queries, network calls, or file parsing, and you need many similar instances.
  • Template systems — when users create variations of pre-built templates (documents, emails, configurations) and only modify a few fields from the default.
  • Runtime type determination — when you do not know the exact class at compile time and need to create copies of objects received through an interface.
  • Undo/snapshot functionality — when you need to save and restore object states, cloning provides a natural way to capture snapshots.

Real-World Usage

  • Java Object.clone() — the Cloneable interface and clone() method are built into Java’s object model, though deep copy must be implemented manually.
  • Python copy modulecopy.copy() and copy.deepcopy() provide shallow and deep cloning for any Python object.
  • JavaScript Object.assign() / structuredClone() — used to clone configuration objects and state in frontend frameworks.
  • Unity EngineInstantiate() clones game objects with all their components, used heavily for spawning enemies, projectiles, and particles.
  • Spring Framework — prototype-scoped beans create a new clone for each injection point, unlike the default singleton scope.



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *