Design Patterns · Creational Patterns
Prototype
Ask an existing object to copy itself. Preserve its concrete type while keeping callers independent of its class.
This lesson follows Builder. Builder assembles from steps. Prototype starts with an object that already exists.
Prototype creates new objects by copying existing ones through a common cloning contract. The object handles its own private state and concrete type. The cost is defining exactly what a copy owns.
Copy through the object that knows the details
A page editor duplicates a selected card. The selected object might be a text card, a chart card, or a plugin card. Code outside the card does not know every constructor or private field. A switch on card type grows with every plugin.
interface Card {
clone(): Card;
render(): string;
}
type Style = { color: string };
class TextCard implements Card {
constructor(private title: string, private style: Style) {}
clone(): TextCard {
return new TextCard(this.title, { ...this.style });
}
setColor(color: string) { this.style.color = color; }
render(): string { return this.title + ": " + this.style.color; }
}
function duplicate(selected: Card): Card {
return selected.clone();
}
const original = new TextCard("Welcome", { color: "blue" });
const copy = original.clone();
copy.setColor("red");
original.render(); // Welcome: blue
copy.render(); // Welcome: red
TextCard can read its own private fields. It constructs another TextCard. A subclass with extra state must implement cloning that preserves that state and its concrete type.
Name what the clone shares
A shallow copy creates a new outer object but reuses references to nested objects. A deep copy also copies selected nested state. Neither is automatically correct. Share immutable configuration. Copy mutable state that the new object should own.
Try it
Change the clone's style
copy.style === original.style → true
copy.setColor('red')
original color → red; copy color → red
copy.style === original.style → false
copy.setColor('red')
original color → blue; copy color → red
Copying values and copying object identity are different decisions.
Deep does not mean copy every resource
A socket, subscription, database connection, or user identifier often needs special treatment. A clone may need a fresh identifier and no active subscription. Decide those rules in clone(). JSON serialization does not preserve arbitrary class behavior.
Step through
Follow a cyclic object graph
A card points at its parent
A section owns a card, and the card refers back to that section. Recursively copying each reference without a rule loops forever.
Record a copy before descending
Use a map from original objects to copies. If the original is already in the map, reuse its copy. This preserves shared references and terminates cycles.
copies.set(originalSection, newSection)
copy card → finds copied parent in the map
card.parent = newSection
Define the boundary
An editor may copy cards and sections while sharing one immutable font catalog. The clone boundary follows ownership, not every reachable reference.
mutable document state → copy
immutable font catalog → share
active network subscription → recreate or omit
Reuse configured prototypes
A prototype registry maps a name to a configured object. A client asks for the “warning card” and receives a clone. Store the prototype privately. Returning the prototype itself lets callers corrupt the defaults for the next caller.
class CardRegistry {
private prototypes = new Map<string, Card>();
register(name: string, card: Card) {
this.prototypes.set(name, card.clone());
}
create(name: string): Card {
const prototype = this.prototypes.get(name);
if (!prototype) throw new Error("Unknown card: " + name);
return prototype.clone();
}
}
Check yourself
A registry returns the preset itself instead of a clone. What can a caller damage?
Correct. Mutation changes the shared preset. Return a clone when independent ownership is promised.
Not quite. No independent copy was created.
Map the roles
| Role | In this example | Job |
|---|---|---|
| Prototype | Card | Declares clone(), usually its only creation method |
| Concrete prototype | TextCard | Implements clone(). Copies its own fields, including private ones, and handles nested state, cycles, and resources. |
| Client | duplicate(selected) | Copies any object that implements the prototype interface, without naming its class |
| Prototype registry | CardRegistry | Stores configured prototypes by name and hands out clones |
Check yourself
ChartCard extends TextCard but does not override clone(). What does chartCard.clone() return?
Correct. Each class must override clone() and construct its own type. An inherited clone builds the parent class.
Not quite. Only if the language or the implementation does that. A clone that calls new TextCard(...) builds a TextCard.
Reach for it when you must copy objects you cannot name
- your code must copy objects without depending on their concrete classes. This happens when objects arrive from third-party code through an interface. You cannot depend on classes you do not know.
- subclasses differ only in how their objects are initialized. Instead of
WarningCard,TipCard, andErrorCardsubclasses that only set colors and icons, keep one configured prototype of each and clone it.
Check yourself
A plugin hands your editor objects through a Card interface, and you must duplicate them. Why is Prototype the natural fit?
Correct. Your code sees an interface. Asking the object to clone itself avoids depending on classes you cannot see.
Not quite. The editor does not know those classes or their private fields.
Implement it in five steps
Refactor existing code toward the pattern in this order:
- Create the prototype interface and declare
clone(). In an existing hierarchy, add the method to every class. - Give each prototype class a way to copy an object of its own class, such as a copy constructor. It copies every field and calls the parent's copy logic so the parent's private fields are copied too.
- Implement
clone()in every class, usually as one line that calls that copy logic with its own class. Override it in each subclass, or clones come back as the parent type. - Optionally, build a central prototype registry that stores frequently used prototypes. Make it a factory class or a static method on the base prototype class, and let it search by name or by richer criteria.
- Replace direct calls to subclass constructors with calls to the registry, which returns a clone.
Check yourself
Why does the registry clone a prototype when you register it and again when you request it?
Correct. Both copies protect the stored prototype from outside mutation.
Not quite. The depth of a copy is decided inside clone(), not by how often it is called.
Name what it costs
| You gain | You pay |
|---|---|
| Clone objects without coupling to their concrete classes | Each class must implement a correct clone |
| Skip repeated initialization by cloning pre-built prototypes | Objects with circular references are tricky to clone |
| Produce complex objects more conveniently | Nested ownership and live resources need a copy policy |
| Handle configuration presets without a subclass per preset | Copying can still be expensive. Measure the actual work. |
Check yourself
A section owns cards, and each card points back to its section. A recursive clone loops forever. What fixes it?
Correct. The map ends the cycle and keeps shared references shared in the copy.
Not quite. That leaves the cards shared with the original, so edits leak between them.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Factory Method | Factory Method relies on inheritance and needs no initialization step after creation. Prototype avoids inheritance, but a clone can need complicated initialization. |
| Abstract Factory | Its creation methods are often factory methods, but they can also clone stored prototypes. |
| Command | Prototype helps save copies of commands into a history. |
| Composite and Decorator | Designs that use these heavily benefit from Prototype. Cloning a finished structure is easier than rebuilding it from scratch. |
| Memento | Prototype can be a simpler memento when the state is simple and holds no links to external resources, or links that are easy to re-establish. |
| Singleton | A prototype registry, like an abstract factory or a builder, can be implemented as a singleton. |
Check yourself
An undo history must keep a copy of each command as it was executed. Which pattern helps make those copies?
Correct. Cloning each command preserves its state at that moment without coupling the history to command classes.
Not quite. A single shared instance is the opposite of keeping independent copies.
Retrieve and apply
Check yourself
A duplicate has a new outer object, but editing its items array changes the original. What failed?
Correct. New identity at the top does not give the array a new identity. Copy the array, and copy its elements too if they are mutable and independently owned.
Not quite. Choosing a constructor does not establish a copy policy. The shared array is the problem.
Choose one object from a small application. Mark each field as copy, share, regenerate, or omit. Continue to Singleton for the opposite creation constraint: callers deliberately receive the same object.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Prototype, printed pages 119–131. Explanations, examples, and exercises are adapted for this course.