Prototype
The Prototype pattern creates new objects by cloning an existing instance rather than constructing one from scratch. When constructing an object is expensive — because it requires a database lookup, network call, or lengthy computation — and you need many similar instances, cloning a pre-built template is faster than repeating that work for each new object.
The pattern also solves a coupling problem. If a caller knows only that it holds a reference to
some interface, it cannot call new ConcreteTypeItDoesntKnowAbout(...) to copy it. But if the
interface exposes a clone() method, the caller can duplicate the object without knowing its
runtime type.
Each concrete class implements clone() by copying itself. The caller holds a Prototype
reference and calls clone() without knowing whether it is duplicating a DocumentTemplate
or a ReportTemplate.
Shallow copy vs deep copy
The central design decision in any clone() implementation is how to treat nested mutable
objects. A shallow copy copies the object's own fields by value but shares references to
any nested objects. A deep copy recursively copies every nested object as well.
The distinction matters when the cloned object is then modified. With a shallow clone, mutating a nested list or map in one copy changes the same underlying structure in the original — the two copies are not truly independent. With a deep clone, each copy owns its own data.
Consider a DocumentTemplate that holds a list of section objects and a style dictionary. If
two editors each clone the template and then modify their own copy, a shallow clone causes their
changes to interfere with each other. A deep clone prevents this.
The trade-off is performance. A deep copy of a document with many nested objects is more expensive than a shallow copy. When the nested data is read-only or immutable, a shallow copy is safe and faster. When callers may mutate the nested objects, a deep copy is required.
Bad → Good
Without the Prototype pattern, a caller that wants a modified copy of a pre-configured object must either reconstruct it from scratch (repeating the setup) or reach into its internals to extract values and re-pass them to a constructor.
1# Bad: reconstructing from scratch couples the caller to all constructor
2# parameters and repeats any expensive setup.
3class DocumentTemplate:
4 def __init__(self, title: str) -> None:
5 self.title = title
6 self.sections: list[str] = []
7 self.styles: dict[str, str] = {}
8 self._load_styles_from_db() # expensive — hits the database
9
10 def _load_styles_from_db(self) -> None:
11 # Simulate a slow lookup
12 self.styles = {"font": "Arial", "size": "12pt", "color": "#333"}
13
14# Every copy forces a database hit, and the caller must know all parameters.
15quarterly_report = DocumentTemplate("Q1 Report")
16quarterly_report.sections = ["Executive Summary", "Financials"]
17
18# To make a similar Q2 report, we repeat the whole construction:
19q2_report = DocumentTemplate("Q2 Report") # another DB hit
20q2_report.sections = ["Executive Summary", "Financials"] # manually copied
With Prototype, the expensive setup happens once. Callers clone the cached template and adjust
only what differs. The deep copy in Python uses copy.deepcopy, which recursively copies every
nested object so that modifications to one clone do not affect the original or other clones.