Prototype
Prototype pattern: clone existing objects instead of building new ones. When deep copy beats constructor.
Design patterns — Prototype
EXAMPLE
// ===== The idea =====
// Create new objects by COPYING an existing one (the prototype),
// instead of constructing from scratch.
//
// Useful when:
// - Construction is expensive (database hydration, network calls)
// - You have a 'template' object configured by the user
// - You want clones with shared starting state then independent edits
// ===== Cloneable interface =====
interface Cloneable<T> {
clone(): T;
}
class Sprite implements Cloneable<Sprite> {
constructor(
public x: number,
public y: number,
public texture: string,
public scripts: string[] = []
) {}
clone(): Sprite {
return new Sprite(this.x, this.y, this.texture, [...this.scripts]);
}
}
const template = new Sprite(0, 0, 'hero.png', ['move', 'jump']);
const enemy1 = template.clone();
enemy1.x = 100;
enemy1.scripts.push('shoot');
// template.scripts is still ['move', 'jump']
// ===== Deep vs shallow =====
// Shallow: clone the top-level object; nested refs shared
const shallow = { ...obj };
// Deep: clone everything (structuredClone in modern JS):
const deep = structuredClone(obj);
// Or: JSON.parse(JSON.stringify(obj)) — works for plain JSON
// ===== Prototype registry (named templates) =====
class PrototypeRegistry<T extends Cloneable<T>> {
private prototypes = new Map<string, T>();
register(name: string, p: T) { this.prototypes.set(name, p); }
create(name: string): T {
const p = this.prototypes.get(name);
if (!p) throw new Error('unknown ' + name);
return p.clone();
}
}
const registry = new PrototypeRegistry<Sprite>();
registry.register('hero', new Sprite(0, 0, 'hero.png', ['move', 'jump']));
registry.register('enemy', new Sprite(0, 0, 'enemy.png', ['patrol']));
const player = registry.create('hero');
const orc = registry.create('enemy'); orc.x = 200;
// ===== When Prototype wins =====
// - Game-dev style: many copies of pre-configured entities
// - Document templates: 'New' clones a starter doc
// - Test factories: hydrate a base object, tweak per-test
// - Avoiding heavy constructors (DB lookup) when starting from known state
// ===== When NOT to use =====
// - Plain objects with cheap constructors (just construct)
// - When deep cloning is hard (DOM nodes, sockets, circular refs)
// - When 'with' expressions or factory functions are clearer
// ===== Compared to =====
// Factory Method: build via subclass; Prototype: build by cloning an instance
// Builder: step-by-step construction; Prototype: bulk copy of an existing one
// Memento: snapshot for undo; Prototype: snapshot to spawn new
// ===== Modern JS / TS twist =====
// 'structuredClone(obj)' is the deep-clone built-in (Node + browsers).
// Records with 'with' expressions (e.g. in C#) cover non-destructive update.
// Immutable libs (Immer) use copy-on-write under the hood.
// ===== Patterns to internalise =====
// - Implement clone() returning the same class type
// - Register templates centrally for reuse
// - Pick deep vs shallow explicitly; document it
// - Use structuredClone for plain objects in JS
// ===== Pitfalls =====
// - Shallow clone when deep was needed (shared nested refs)
// - Clone leaking handles / sockets / DOM references
// - Modifying the template via the clone (shared array)
// - Using clone() to bypass constructor invariants
Why it matters
Prototype builds new objects by cloning a template. Useful when construction is expensive or many copies of a configured object are needed. Decide deep vs shallow deliberately, register templates centrally, and never let clones mutate template state. Modern languages often provide records with with-expressions that hit the same target with less ceremony.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Clone an existing object as the seed for a new one.
const base = { theme: 'light', fontSize: 14 };
const dark = { ...base, theme: 'dark' };
Try it Yourself »
Discussion
Loading…