Home Design Patterns Prototype
Prototype
Creational Gang of Four“Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.”
Gamma, Helm, Johnson & Vlissides, p. 117Purpose
Prototype creates objects by copying an existing one rather than by calling a constructor. The instance you copy has already been configured, so the copy starts life valid and populated, and the code doing the copying does not need to know what any of that configuration means.
This matters when the interesting variations are data rather than code. If the set of configurations is known when you write the program, a class or a constructor argument expresses it perfectly well. If the set is discovered by whoever is using the system, at a rate you do not control, then no amount of class design will keep up, and copying a known-good instance is the only approach that scales.
Design
One operation, clone(), declared on a common interface, with each
class responsible for copying itself. A registry of named prototypes usually sits
alongside it, so callers ask for "the fine-pitch template" rather than holding
prototype objects directly.
clone(). It
never learns which concrete class it has, and it never learns what the
configuration it just duplicated actually meant.The design work is not in the diagram. It is in deciding, per field, whether the copy gets its own instance or shares the original's. Copy everything and you pay for it in time and memory and lose intentional sharing. Copy nothing and the "new" object is quietly wired into the old one's internals. Neither answer is right for a whole object; the answer is per field, and it has to be written down somewhere, because nothing in the language records it.
Example
Autobot Robotics customers configure jobs on the shop floor: tool offsets, speed profile, dwell times, the safety envelope, and a vision recipe. A job carries a few hundred settings and the combinations that work are found by an experienced operator with a scrap bin, not derived from first principles by anyone in the office.
The first attempt shipped named presets defined in code. Every time a customer found a configuration worth keeping, it became a support request, then a change request, then a software release. The operators did the sensible thing and stopped asking, keeping their variations in a notebook instead, which is the worst possible place for them. So the scheduler grew a template library: any job can be saved as a template, and creating a job means cloning a template and changing the two fields that differ.
The first version of clone() copied the reference to the safety
envelope rather than the envelope itself. An operator tightened the envelope on a
copy for a delicate part, and every job cloned from that template, including ones
running on other cells, tightened with it. Nothing crashed, and the machines
simply ran slower and nobody could say why for two days. Shallow copying is not a
performance choice; it is a decision about identity, and it needs to be made
deliberately for each field.
class JobTemplate
{
string name;
SpeedProfile speed;
SafetyEnvelope envelope;
VisionRecipe recipe;
public JobTemplate clone()
{
JobTemplate copy = new JobTemplate();
copy.name = name;
copy.speed = speed.clone(); // deep: each job retunes this
copy.envelope = envelope.clone(); // deep: this is the one that bit us
copy.recipe = recipe; // shallow: immutable, shared on purpose
return copy;
}
}
class TemplateLibrary
{
Dictionary<string, JobTemplate> prototypes;
public void register(string key, JobTemplate t)
{
prototypes[key] = t;
}
public JobTemplate create(string key)
{
return prototypes[key].clone();
}
}
clone(). The pattern is the four
assignments inside it, three of which are a considered decision and one of
which was not. TemplateLibrary is the part that makes it usable:
callers name a template and never touch a prototype object.Seen in the Wild
JavaScript is the language that took this seriously: objects inherit directly
from other objects, and Object.create() makes a new one from an
existing instance with no class involved at all. Java's
Cloneable and Object.clone() are the pattern shipped as
a language feature and are widely regarded as a mistake, for reasons covered
below; .NET's ICloneable has the same trouble and Microsoft's own
guidance is to avoid implementing it. Browsers and Node now provide
structuredClone(), which is a deep copy with the object graph
problem actually solved. In game development, Unity's prefabs are the pattern in
its purest form: a configured instance saved as an asset, and every object in the
scene is a copy of one.
Comparisons
- Abstract Factory can be implemented with prototypes instead of subclasses. Store one configured instance per product and clone on request, and the factory hierarchy disappears entirely.
- Factory Method is the alternative when the variations are code rather than data. Prototype avoids the subclass; Factory Method needs one.
- Memento also copies an object's state, but for restoration rather than reproduction, and it deliberately hides the copy from everyone except the object it came from.
- Flyweight is the exact opposite instinct. Flyweight shares one object between many users; Prototype makes sure every user gets their own. Most deep-copy bugs are a Flyweight that appeared by accident.
- Marker Interface is worth reading
alongside this one, because Java's
Cloneableis the standard example of that idea going wrong.
Why It Exists
My take
Prototype exists because classes are fixed at compile time and the space of useful configurations is not. Every system that gets handed to real users eventually discovers this. The programmer's model is that there are five kinds of job; the operator's model is that there are ninety, they differ in ways the programmer would consider trivial, and the differences are the accumulated result of a decade of finding out what actually works. You cannot express ninety subclasses. You can express one object and a copy operation.
The second reason is subtler and I think more important: a constructor requires the caller to know how to build a valid object, and often nobody does. A job that runs correctly is the product of tuning, and the knowledge lives in the running instance and nowhere else. Copying is how you propagate knowledge you cannot articulate. That is a genuinely different justification from the rest of the creational patterns, all of which assume that somebody, somewhere, understands the parameters.
Where I would push back on the book is the implementation. In modern code you
do not write this pattern; you write a copy constructor, or you serialize and
deserialize, or you make the object immutable and provide a with
method that returns a modified copy. The functional-language answer, persistent
data structures with structural sharing, is Prototype and Flyweight solved
together, and it is better than either. What survives is not the
clone() method, it is the discipline of deciding what a copy means
field by field.
In a long-lived system that last part is where the money is. An aliasing bug introduced by a careless shallow copy does not fail fast. It sits there, correlating two objects that the design says are independent, until someone changes one of them in production. The two days lost to the safety envelope above were cheap by the standards of this class of bug.
Criticisms
The operation has no agreed meaning. clone() does not say whether
it is deep or shallow, and no type system checks, so every implementation is a
convention that a caller has to look up in the source. Java made this worse by
putting clone() on Object as protected and gating it
behind Cloneable, an interface that declares no methods, so the
compiler cannot even tell you whether an object supports the operation. Joshua
Bloch's advice to avoid the whole mechanism and write a copy constructor instead
is correct, and it is advice about a language feature that exists purely to
support this pattern.
Deep copying also assumes the object graph is a tree, and it usually is not. Cycles hang a naive implementation, back-references get duplicated when they should not be, and any object holding something uncopyable, a file handle, a socket, a lock, a device register mapping, has no correct answer available. On the embedded side this is not theoretical: cloning something that owns a hardware resource produces two objects that both believe they control one piece of silicon.
Finally the pattern is a poor fit wherever object identity carries meaning. If your database rows, audit records, or serial-numbered parts are objects, a copy operation is an invitation to create a duplicate that looks authoritative and is not. I would rather make those types uncopyable and be forced to write the explicit "new record derived from that one" code, which is at least visible when someone reads it later.