iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…