You’re building a game. Each level needs thousands of similar enemies: same stats, same abilities, minor variations. Creating each from scratch is slow. Configuration is complex.

What if you could just copy a template enemy and tweak it?

What is the Prototype Pattern?

Prototype creates new objects by cloning an existing object. You create a prototype, configure it once, then clone it to create new instances. The clone has the same state as the original.

Prototype Design Pattern class diagram showing Prototype interface with clone method, concrete implementations PrototypeA and PrototypeB, and Client that clones prototypes

The client works with the Prototype interface. It doesn’t know the concrete class being cloned.

When to Use Prototype

Use Prototype When Skip Prototype When
Object creation is expensive Creating objects is cheap
You need copies of configured objects Each object needs unique setup
You want to reduce subclasses Inheritance works fine
Concrete classes should be hidden Class visibility isn’t a concern

Prototype is about copying, not constructing from scratch.

Implementation

Basic Prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public interface Prototype<T> {
    T clone();
}

public class Enemy implements Prototype<Enemy> {
    private String name;
    private int health;
    private int attack;
    private int defense;
    private List<String> abilities;
    private Position position;
    
    public Enemy(String name, int health, int attack, int defense) {
        this.name = name;
        this.health = health;
        this.attack = attack;
        this.defense = defense;
        this.abilities = new ArrayList<>();
        this.position = new Position(0, 0);
    }
    
    // Private copy constructor
    private Enemy(Enemy source) {
        this.name = source.name;
        this.health = source.health;
        this.attack = source.attack;
        this.defense = source.defense;
        // Deep copy of mutable objects
        this.abilities = new ArrayList<>(source.abilities);
        this.position = new Position(source.position.x, source.position.y);
    }
    
    @Override
    public Enemy clone() {
        return new Enemy(this);
    }
    
    public void addAbility(String ability) {
        abilities.add(ability);
    }
    
    public void setPosition(int x, int y) {
        this.position = new Position(x, y);
    }
    
    // Getters...
}

// Usage
Enemy goblinPrototype = new Enemy("Goblin", 50, 10, 5);
goblinPrototype.addAbility("Quick Attack");
goblinPrototype.addAbility("Dodge");

// Clone for each instance
Enemy goblin1 = goblinPrototype.clone();
goblin1.setPosition(100, 200);

Enemy goblin2 = goblinPrototype.clone();
goblin2.setPosition(150, 220);

// Each goblin has same stats but different positions

Prototype Registry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class PrototypeRegistry {
    private final Map<String, Prototype<?>> prototypes = new HashMap<>();
    
    public void register(String key, Prototype<?> prototype) {
        prototypes.put(key, prototype);
    }
    
    @SuppressWarnings("unchecked")
    public <T extends Prototype<T>> T get(String key) {
        Prototype<?> prototype = prototypes.get(key);
        if (prototype == null) {
            throw new IllegalArgumentException("Unknown prototype: " + key);
        }
        return (T) prototype.clone();
    }
}

// Usage
PrototypeRegistry enemies = new PrototypeRegistry();

// Register prototypes once
Enemy goblin = new Enemy("Goblin", 50, 10, 5);
goblin.addAbility("Quick Attack");
enemies.register("goblin", goblin);

Enemy orc = new Enemy("Orc", 100, 20, 15);
orc.addAbility("Smash");
orc.addAbility("Rage");
enemies.register("orc", orc);

Enemy dragon = new Enemy("Dragon", 500, 50, 40);
dragon.addAbility("Fire Breath");
dragon.addAbility("Fly");
dragon.addAbility("Tail Sweep");
enemies.register("dragon", dragon);

// Spawn enemies by cloning
Enemy enemy1 = enemies.get("goblin");
Enemy enemy2 = enemies.get("orc");
Enemy boss = enemies.get("dragon");

Deep Copy for Complex Objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class GameCharacter implements Prototype<GameCharacter> {
    private String name;
    private Stats stats;
    private Inventory inventory;
    private List<Skill> skills;
    private Map<String, Integer> attributes;
    
    // Copy constructor with deep cloning
    private GameCharacter(GameCharacter source) {
        this.name = source.name;
        this.stats = source.stats.clone();  // Stats must also be cloneable
        this.inventory = source.inventory.clone();  // Deep copy
        this.skills = source.skills.stream()
            .map(Skill::clone)
            .collect(Collectors.toList());
        this.attributes = new HashMap<>(source.attributes);
    }
    
    @Override
    public GameCharacter clone() {
        return new GameCharacter(this);
    }
}

public class Stats implements Prototype<Stats> {
    private int strength;
    private int agility;
    private int intelligence;
    
    private Stats(Stats source) {
        this.strength = source.strength;
        this.agility = source.agility;
        this.intelligence = source.intelligence;
    }
    
    @Override
    public Stats clone() {
        return new Stats(this);
    }
}

public class Inventory implements Prototype<Inventory> {
    private List<Item> items;
    private int gold;
    
    private Inventory(Inventory source) {
        this.items = source.items.stream()
            .map(Item::clone)
            .collect(Collectors.toList());
        this.gold = source.gold;
    }
    
    @Override
    public Inventory clone() {
        return new Inventory(this);
    }
}

Using Java’s Cloneable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Document implements Cloneable {
    private String title;
    private String content;
    private List<String> authors;
    private Date createdAt;
    
    @Override
    public Document clone() {
        try {
            Document cloned = (Document) super.clone();
            // Deep copy mutable fields
            cloned.authors = new ArrayList<>(this.authors);
            cloned.createdAt = (Date) this.createdAt.clone();
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Should not happen", e);
        }
    }
}

Note: Java’s Cloneable is considered problematic. Most prefer copy constructors or custom clone methods.

Configuration Templates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class HttpRequestTemplate implements Prototype<HttpRequestTemplate> {
    private String baseUrl;
    private Map<String, String> headers;
    private Duration timeout;
    private int retries;
    
    private HttpRequestTemplate(HttpRequestTemplate source) {
        this.baseUrl = source.baseUrl;
        this.headers = new HashMap<>(source.headers);
        this.timeout = source.timeout;
        this.retries = source.retries;
    }
    
    public HttpRequestTemplate() {
        this.headers = new HashMap<>();
        this.timeout = Duration.ofSeconds(30);
        this.retries = 3;
    }
    
    @Override
    public HttpRequestTemplate clone() {
        return new HttpRequestTemplate(this);
    }
    
    // Builder methods for customization after cloning
    public HttpRequestTemplate withPath(String path) {
        HttpRequestTemplate copy = this.clone();
        copy.baseUrl = this.baseUrl + path;
        return copy;
    }
    
    public HttpRequestTemplate withHeader(String key, String value) {
        HttpRequestTemplate copy = this.clone();
        copy.headers.put(key, value);
        return copy;
    }
}

// Setup base template
HttpRequestTemplate apiTemplate = new HttpRequestTemplate();
apiTemplate.setBaseUrl("https://api.example.com");
apiTemplate.addHeader("Authorization", "Bearer token123");
apiTemplate.addHeader("Content-Type", "application/json");

// Clone and customize for specific endpoints
HttpRequestTemplate usersRequest = apiTemplate.clone()
    .withPath("/users");

HttpRequestTemplate ordersRequest = apiTemplate.clone()
    .withPath("/orders")
    .withHeader("X-Custom-Header", "value");

How It Works

sequenceDiagram
    participant Client
    participant Registry as PrototypeRegistry
    participant Prototype as GoblinPrototype
    participant Clone as GoblinClone
    
    Client->>Registry: get("goblin")
    Registry->>Prototype: clone()
    Prototype->>Clone: new Enemy(this)
    Note over Clone: Copy all fields
    Clone-->>Registry: Clone instance
    Registry-->>Client: Enemy clone
    
    Client->>Clone: setPosition(100, 200)
    Note over Clone: Customize clone

Common Mistakes

1. Shallow Copy Instead of Deep Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Wrong - shallow copy shares references
private Enemy(Enemy source) {
    this.name = source.name;
    this.abilities = source.abilities;  // Same list!
}

// Modifying one affects the other
clone.abilities.add("New Skill");  // Also adds to original!

// Right - deep copy
private Enemy(Enemy source) {
    this.name = source.name;
    this.abilities = new ArrayList<>(source.abilities);  // New list
}

2. Forgetting Mutable Fields

1
2
3
4
5
6
7
8
9
10
11
// Missing deep copy for mutable Date
private Document(Document source) {
    this.title = source.title;  // String is immutable, OK
    this.createdAt = source.createdAt;  // Date is mutable, WRONG
}

// Right
private Document(Document source) {
    this.title = source.title;
    this.createdAt = new Date(source.createdAt.getTime());  // Copy
}

3. Circular References

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Tricky - nodes reference each other
public class TreeNode implements Prototype<TreeNode> {
    private TreeNode parent;
    private List<TreeNode> children;
    
    @Override
    public TreeNode clone() {
        // Need to handle parent/child relationships carefully
        TreeNode clone = new TreeNode();
        clone.children = children.stream()
            .map(TreeNode::clone)
            .peek(child -> child.parent = clone)  // Fix parent references
            .collect(Collectors.toList());
        return clone;
    }
}

Real-World Examples

Java Object.clone(): Built-in cloning mechanism.

JavaScript Object Spread: {...obj} creates a shallow copy.

Spreadsheet Templates: Clone a template row/sheet with formulas.

IDE Project Templates: Clone project structure with default files.

Abstract Factory can use prototypes instead of constructors.

Builder creates complex objects step by step. Prototype copies existing complex objects.

Memento captures object state for later restoration. Prototype copies for new instances.

Wrapping Up

Prototype creates new objects by copying existing ones. It’s useful when construction is expensive, when you have pre-configured templates, or when you want to hide concrete classes.

Remember to implement deep copy for mutable fields. Shallow copies lead to shared state bugs.

Use a prototype registry when you have multiple prototype types to manage.


Further Reading: