5. Prototype
The Prototype pattern creates new objects by copying an existing object (the prototype) rather than instantiating from scratch. It is especially useful when object creation is expensive or when new objects differ only slightly from existing ones.
Intent and Motivation
Intent: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
Motivation: Sometimes creating an object from scratch is costly — loading data from a database, parsing a large configuration file, or initializing a complex graph. If the new object is nearly identical to an existing one, cloning is faster and simpler. Prototype also avoids subclassing the creator for every product variant: instead of new Monster("goblin") vs new Monster("orc"), you clone a preconfigured goblin prototype and tweak a few fields.
In JavaScript, prototypal inheritance is built into the language; the design pattern formalizes the same idea for class-based systems.
Structure (UML-like)
┌──────────────────┐
│ Prototype │ (interface)
├──────────────────┤
│ + clone(): │
│ Prototype │
└────────▲─────────┘
│ implements
┌────────┴─────────┐ manages ┌──────────────────┐
│ ConcretePrototype│ ◄───────────────────── │ PrototypeRegistry│
├──────────────────┤ ├──────────────────┤
│ + clone() │ │ + register(key, │
│ + customField │ │ prototype) │
└──────────────────┘ │ + clone(key) │
└──────────────────┘
Participants:
- Prototype — declares a
clone()method. - ConcretePrototype — implements cloning; handles deep vs shallow copy of fields.
- Client — requests a new object by calling
clone()on a prototype. - PrototypeRegistry (optional) — stores named prototypes for lookup and cloning.
Java Example
import java.util.HashMap;
import java.util.Map;
class Shape implements Cloneable {
String type;
int x, y;
Shape(String type, int x, int y) {
this.type = type;
this.x = x;
this.y = y;
}
@Override
public Shape clone() {
try {
return (Shape) super.clone(); // shallow copy
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
void move(int dx, int dy) { x += dx; y += dy; }
@Override
public String toString() {
return type + " at (" + x + "," + y + ")";
}
}
class ShapeRegistry {
private final Map<String, Shape> prototypes = new HashMap<>();
void register(String key, Shape prototype) {
prototypes.put(key, prototype);
}
Shape create(String key) {
Shape proto = prototypes.get(key);
if (proto == null) throw new IllegalArgumentException(key);
return proto.clone();
}
}
// Usage
ShapeRegistry registry = new ShapeRegistry();
registry.register("circle", new Shape("Circle", 0, 0));
registry.register("square", new Shape("Square", 10, 10));
Shape c1 = registry.create("circle");
Shape c2 = registry.create("circle");
c1.move(5, 5);
System.out.println(c1); // Circle at (5,5)
System.out.println(c2); // Circle at (0,0) — independent copy
For deep copy of nested objects, override clone() to recursively clone references.
JavaScript Example
class GameEnemy {
constructor(template) {
this.name = template.name;
this.health = template.health;
this.weapons = [...template.weapons]; // deep copy array
this.position = { ...template.position }; // deep copy object
}
static prototypes = {
goblin: { name: 'Goblin', health: 30, weapons: ['dagger'], position: { x: 0, y: 0 } },
orc: { name: 'Orc', health: 80, weapons: ['axe', 'shield'], position: { x: 0, y: 0 } }
};
static spawn(type, x, y) {
const proto = this.prototypes[type];
const enemy = new GameEnemy(proto);
enemy.position = { x, y };
return enemy;
}
}
const g1 = GameEnemy.spawn('goblin', 10, 20);
const g2 = GameEnemy.spawn('goblin', 50, 60);
g1.health = 0;
console.log(g2.health); // 30 — independent instance
Modern JavaScript also provides structuredClone(obj) for deep cloning plain objects and many built-in types.
Real-World Use Cases
| Framework / System | Usage |
|---|---|
Java Object.clone() |
Built-in shallow clone mechanism (requires Cloneable marker interface). |
| JavaScript | Object.create(proto) — prototypal inheritance is language-native. |
| Game engines | Enemy/NPC spawning clones preconfigured templates with varied positions. |
| Document editors | “Duplicate slide” or “Copy shape” clones an existing element graph. |
Spring @Scope("prototype") |
Each getBean() call returns a new instance (opposite of singleton). |
| Object pools | Reset and reuse cloned instances instead of garbage-collecting and re-allocating. |
Pros and Cons
| Pros | Cons |
|---|---|
| Cloning is faster than full initialization for expensive objects | Deep copy logic can be complex with nested references |
| Reduces subclassing — configure prototypes instead | Cloneable in Java is widely considered a design flaw |
| Adds/removes products at runtime via prototype registry | Shallow copy bugs — shared mutable state between clones |
| Hides concrete class names from clients | Circular references make deep cloning difficult |
| Works well with object pools and template systems | Not all languages have built-in clone support |
When to Use vs When NOT to Use
Use when:
- Object creation is expensive (I/O, computation) and clones are cheaper.
- Systems should be independent of how products are composed and created.
- Classes to instantiate are specified at runtime (e.g., by user configuration).
- You want to avoid building large class hierarchies of factories for minor variations.
Do NOT use when:
- Objects are simple and cheap to construct with
new. - Deep copy requirements make cloning more complex than re-creating.
- Every instance is unique with no shared template (no benefit from cloning).
- Immutability makes copying unnecessary — just reuse the same instance.
Common Mistakes
- Shallow copy of mutable fields — two clones share the same nested list or map; mutating one affects the other.
- Implementing
Cloneablewithout overridingclone()— gets shallowObject.clone()with no custom logic. - Cloning objects with open resources — file handles, sockets, or DB connections should not be blindly duplicated.
- Ignoring circular references — infinite recursion during deep clone.
- Using Prototype when Factory Method would be clearer — if construction logic varies significantly, factories are better.
Related Patterns
- Factory Method / Abstract Factory — create objects from scratch; Prototype creates from existing instances.
- Memento — saving state often uses cloning techniques similar to Prototype.
- Flyweight — shares intrinsic state; Prototype copies full instances (opposite trade-off).
- Builder — constructs step by step; Prototype copies in one operation.
- Prototype Registry — often combined with Singleton registry for global prototype lookup.