Low-level designDeep dive8 min

Prototype

To duplicate an object without knowing its class, ask the object to copy itself. Each class implements one copy method that knows its own fields, so the code that wants a copy never has to.

Watch it evolve1 / 9
The problem

Duplicate the selection

The editor copies each selected object and moves the copy ten tiles to the right.

80
Start
Stage 1
Final

1 · The situation

Duplicating a selection in a level editor

A small game's level editor lets a designer select some placed objects and duplicate them ten tiles to the right. Objects come in different kinds: a Turret sits at one point with a firing range; a Patrol walks a route of points. Both implement Placeable, and the editor works with that interface.

To duplicate, Editor needs a copy of each object. The first version makes copies itself, in copyOf(), by checking each object's class and calling the matching constructor. Python writes move_by(); the other names are the same in all four languages.

The first versionJava
import java.util.ArrayList;
import java.util.List;

record Point(int x, int y) {
    Point shift(int dx) {
        return new Point(x + dx, y);
    }
}

interface Placeable {
    void moveBy(int dx);
    String describe();
}

class Turret implements Placeable {
    private Point at;
    private final int range;

    Turret(Point at, int range) {
        this.at = at;
        this.range = range;
    }

    Point at() { return at; }
    int range() { return range; }

    public void moveBy(int dx) {
        at = at.shift(dx);
    }

    public String describe() {
        return "Turret at " + at.x() + "," + at.y() + ", range " + range;
    }
}

class Patrol implements Placeable {
    private final List<Point> route;

    Patrol(List<Point> route) {
        this.route = new ArrayList<>(route);
    }

    List<Point> route() { return route; }

    public void moveBy(int dx) {
        route.replaceAll(p -> p.shift(dx));
    }

    public String describe() {
        List<String> stops = new ArrayList<>();
        for (Point p : route) stops.add(p.x() + "," + p.y());
        return "Patrol " + String.join(" -> ", stops);
    }
}

class Editor {
    List<Placeable> duplicate(List<Placeable> selection, int dx) {
        List<Placeable> copies = new ArrayList<>();
        for (Placeable p : selection) {
            Placeable copy = copyOf(p);
            copy.moveBy(dx);
            copies.add(copy);
        }
        return copies;
    }

    private Placeable copyOf(Placeable p) {
        if (p instanceof Turret t) return new Turret(t.at(), t.range());
        if (p instanceof Patrol r) return new Patrol(r.route());
        throw new IllegalArgumentException("can't copy " + p.describe());
    }
}

public class Main {
    public static void main(String[] args) {
        List<Placeable> selection = List.of(
                new Turret(new Point(2, 3), 5),
                new Patrol(List.of(new Point(6, 1), new Point(9, 1))));
        List<Placeable> copies = new Editor().duplicate(selection, 10);
        for (Placeable p : selection) System.out.println(p.describe());
        for (Placeable p : copies) System.out.println(p.describe());
    }
}
1*«create»«create»«record»Point− x: int− y: int~ shift(int): Point«interface»Placeable+ moveBy(int)+ describe(): StringTurret− at: Point− range: int~ at(): Point~ range(): int+ moveBy(int)+ describe(): StringPatrol− route: List<Point>~ route(): List<Point>+ moveBy(int)+ describe(): StringEditor~ duplicate(List<Placeable>, int): List<Placeable>− copyOf(Placeable): Placeable

The class diagram of this version, drawn from its code: the editor depends on every kind of object it copies.

Turret at 2,3, range 5
Patrol 6,1 -> 9,1
Turret at 12,3, range 5
Patrol 16,1 -> 19,1

The originals, then the copies, ten tiles to the right.

2 · The change

Every new kind of object reaches into the editor

The designers want spawners next: a point that releases a wave every few seconds. A Spawner class is easy. Duplicating it isn't, because copyOf() names every class it can copy:

The editor knows every classJava
class Editor {
    List<Placeable> duplicate(List<Placeable> selection, int dx) {
        List<Placeable> copies = new ArrayList<>();
        for (Placeable p : selection) {
            Placeable copy = copyOf(p);
            copy.moveBy(dx);
            copies.add(copy);
        }
        return copies;
    }

    private Placeable copyOf(Placeable p) {
        if (p instanceof Turret t) return new Turret(t.at(), t.range());
        if (p instanceof Patrol r) return new Patrol(r.route());
        throw new IllegalArgumentException("can't copy " + p.describe());
    }
}

The costs go beyond one more if:

  • The editor depends on every kind of object. A new kind edits the editor; a forgotten one fails only when someone duplicates it.
  • Each class exposes its insides. Turret has at() and range(), and Patrol hands out its route, only so that the editor can copy them.
  • The editor decides how deep to copy. Patrol's constructor happens to copy the route list. If it didn't, moving the copy would move the original's route too, and the editor would never know.

The practice checks flag the type test, and recognize copyOf() as a simple factory: a method that creates one of several classes behind a common type.

3 · The fix

Each object copies itself

The object being copied is the only one that knows all its fields and which of them need copying too. Give that job to it:

Placeable gains copy(), which returns a new, independent object of the same kind. Each class implements it with its own fields: Turret passes its point and range; Patrol passes its route, which its constructor copies.

Java
class Turret implements Placeable {
    private Point at;
    private final int range;

    Turret(Point at, int range) {
        this.at = at;
        this.range = range;
    }

    public Placeable copy() {
        return new Turret(at, range);
    }

    public void moveBy(int dx) {
        at = at.shift(dx);
    }

    public String describe() {
        return "Turret at " + at.x() + "," + at.y() + ", range " + range;
    }
}

class Patrol implements Placeable {
    private final List<Point> route;

    Patrol(List<Point> route) {
        this.route = new ArrayList<>(route);
    }

    public Placeable copy() {
        return new Patrol(route);
    }

    public void moveBy(int dx) {
        route.replaceAll(p -> p.shift(dx));
    }

    public String describe() {
        List<String> stops = new ArrayList<>();
        for (Point p : route) stops.add(p.x() + "," + p.y());
        return "Patrol " + String.join(" -> ", stops);
    }
}
1*«record»Point− x: int− y: int~ shift(int): Point«interface»Placeable+ copy(): Placeable+ moveBy(int)+ describe(): StringTurret− at: Point− range: int+ copy(): Placeable+ moveBy(int)+ describe(): StringPatrol− route: List<Point>+ copy(): Placeable+ moveBy(int)+ describe(): StringEditor~ duplicate(List<Placeable>, int): List<Placeable>
Turret at 2,3, range 5
Patrol 6,1 -> 9,1
Turret at 12,3, range 5
Patrol 16,1 -> 19,1

The same originals and copies as before.

A Spawner now implements copy() like the others, and the editor duplicates it without a change.

4 · The pattern

Prototype: copies made by the original

Prototype creates objects by copying an existing instance, the prototype, through a method every kind implements. The client decides when to copy; each class decides how. That works even when the client has only an interface, such as a selection of Placeable objects, and doesn't know which classes are in it.

1*«record»Point− x: int− y: int~ shift(int): Point«interface»Placeable+ copy(): Placeable+ moveBy(int)+ describe(): StringTurret− at: Point− range: int+ copy(): Placeable+ moveBy(int)+ describe(): StringPatrol− route: List<Point>+ copy(): Placeable+ moveBy(int)+ describe(): StringEditor~ duplicate(List<Placeable>, int): List<Placeable>

The roles in the editor's code: Placeable is the prototype interface, and each kind of object copies itself.

Role

In the editor

Its job

Prototype

Placeable

Declares copy()

Concrete prototype

Turret, Patrol

Returns an independent copy of itself

Client

Editor

Asks for copies and adjusts them

The editor asks the original for a copy, then moves the copy; the original's route is untouched.

Prototype is also useful when making an object from scratch is expensive or involved: a preset configured once, then copied and adjusted. A palette of ready-made objects that the designer copies from is a prototype registry.

5 · What it costs

Every class must copy itself correctly

  • One more method in every class, and a new field must be added to it too.
  • Deep or shallow is a decision. A copy that shares a mutable list with its original isn't independent; a copy that duplicates a large shared resource wastes memory.
  • Cycles and shared parts are hard. Objects that refer to each other need care, so that copying one doesn't copy the world or lose the links between them.

Field

Copy it?

Why

An immutable value, such as a point or a string

No, share it

Nothing can change it

A mutable collection the object owns, such as a route

Yes

Otherwise changes to the copy reach the original

A shared service or resource, such as a texture

No, share it

It isn't part of the object's state

If objects are immutable, copying is rarely needed: share the original and create changed versions instead. And if the client knows the concrete class, a copy constructor is simpler.

6 · Getting it right

Decide what deep means, class by class

  • Copy what the object owns; share what it uses. The route is owned; a reference to the level's texture atlas isn't.
  • Return the interface type, so the client never needs a cast.
  • Test independence. Change the copy, and check that the original didn't move.
  • Don't rely on a generic deep copy for everything. It copies what it finds, including things meant to be shared.

Each language has its own copying tools, and each has a trap:

Language

The usual shape

The trap

Java

A copy() method or a copy constructor

Object.clone() makes a shallow copy and needs Cloneable; most code avoids it

C++

A virtual clone() that copies the object with its copy constructor, inside std::make_unique

Copies through a base class slice off the subclass's part; members held by value copy deeply, raw pointers don't

JavaScript

A copy() method

Spread copies one level; structuredClone copies deeply but drops the prototype chain and private fields

Python

A copy() method, or copy.copy and copy.deepcopy

copy.copy shares nested lists; customize with __copy__ and __deepcopy__

7 · Look-alikes

Copies, snapshots and prototypes

Several ideas involve copying, and one shares the name without being the pattern.

Idea

What it does

Prototype

Makes a new object by asking an existing one to copy itself, through an interface

Copy constructor

Copies an object whose class the caller knows

Factory Method

Lets a subclass choose which class to create from scratch

Memento

Saves an object's state to restore later, not to make a second object

JavaScript prototypes

The language's object linking for inheritance; a different idea that shares the word

The last row trips people up. In JavaScript every object has a prototype it inherits from; that's how the language does inheritance, and it has nothing to do with copying. The pattern is about making independent copies.

8 · In practice

Presets, palettes and duplicate commands

Editors of every kind use the idea: duplicating a shape in a drawing tool, a slide in a deck, or a row in a spreadsheet, without the duplicate command knowing what it's duplicating. Presets work the same way: a configured object is kept as a template and copied when needed.

None of the practice problems needs to copy objects yet. The checks do flag the symptom this pattern removes: code that tests an object's class to decide how to handle it.

Check yourself

4 questions

1. The designers add a Spawner. What has to change in the Prototype version?
2. Patrol.copy() passes its route list to the new patrol without copying it, and the constructor doesn't copy it either. What happens when the copy moves?
3. Which field should a copy share rather than duplicate?
4. Is JavaScript's prototype chain an example of the Prototype pattern?

Takeaways

Remember this

  • Prototype: ask an object to copy itself, so the client never needs to know its class.
  • Each class knows its own fields and decides what a correct copy is.
  • Copy what the object owns; share immutable values and resources it only uses.
  • Generic copying tools are either too shallow or too deep for some fields; know which you're using.
  • Immutable objects rarely need copying at all.