Skip to main content

Command Palette

Search for a command to run...

The Prototype Design Pattern

Published
4 min read
G

I am a developer with learnings in many different languages, frameworks and technologies.

Have you ever heard of the Prototype Design Pattern?

Today, I will explain this in an easy-to-understand manner.

First, let’s take the following example:

You are creating a team-based first-person shooter game where there will be one real player and the others will be bots. Now, if you think all bots will have almost everything the same, like the texture of head, body, arms, weapons and AI behaviour of the bot, etc, but what will change? The position of the bots, their name and their ID. So you can clearly see that if we simply make one object of type bot, there will be a lot of asset loading behind the scenes because texture and AI behaviour need to be loaded from the disk. Imagine we need to create 10 bots of such type, thus there is 10 times the asset loading that we need to do. Is there any better way to do so?

Yes, there exists a better way, which is known as the Prototype Design Pattern. In that, instead of creating an object from start to end, we simply use the already ready-made object that we created once, known as a prototype and use that object and clone other objects from it (Only works if we make the cloning less expensive, otherwise a bad prototype will do all the stuff again). We will only change the few required fields once we get a new object. This way, we are saving the asset loading time.

Step 1: The expensive class/object:

class Enemy implements Cloneable {

    private Mesh mesh;
    private Texture texture;
    private AnimationSet animations;
    private AIBehavior ai;
    private Weapon weapon;
    private int health;

    public Enemy(Mesh mesh,
                 Texture texture,
                 AnimationSet animations,
                 AIBehavior ai,
                 Weapon weapon,
                 int health) {

        // Imagine all of this takes time and IO
        this.mesh = mesh;
        this.texture = texture;
        this.animations = animations;
        this.ai = ai;
        this.weapon = weapon;
        this.health = health;
    }

    @Override
    protected Enemy clone() {
        try {
            Enemy copy = (Enemy) super.clone();

            // Deep copy where needed
            copy.ai = ai.clone();
            copy.weapon = weapon.clone();

            // Mesh, texture, animations are shared (read-only)
            return copy;

        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Cloning failed", e);
        }
    }
}

Note: In real-world Java code, many teams prefer copy constructors or explicit copy() methods over Cloneable due to its pitfalls.

Step 2: the expensive setup (done once)

// Load assets (disk, GPU, animations)
Mesh orcMesh = MeshLoader.load("orc.mesh");
Texture orcTexture = TextureLoader.load("orc.png");
AnimationSet orcAnimations = AnimationLoader.load("orc.anim");

// Build AI and weapon
AIBehavior aggressiveAI = new AggressiveAI();
Weapon axe = new Axe(25);

// Create ONE fully-initialized enemy
Enemy orcPrototype = new Enemy(
    orcMesh,
    orcTexture,
    orcAnimations,
    aggressiveAI,
    axe,
    100
);

Step 3: Spawning enemies using Prototype

List<Enemy> enemies = new ArrayList<>();

for (int i = 0; i < 500; i++) {
    Enemy orc = orcPrototype.clone();
    enemies.add(orc);
}

Dilemma of Deep Copy vs. Shallow Copy:

In languages like Java, where objects are references, if you do not deep copy objects, then weird behaviour will happen because your main object will be cloned, but inside it, the other objects will be using the same references. So always thought about for an object what you want to clone: a deep or shallow copy.

Advantages:

  1. Faster Object Creation: Operations such as disk reading are done once to load something, then cloning won’t invoke the same operations again.

  2. Avoid complex constructors and factories.

  3. Guaranteed Consistency: Once a consistent prototype is made, the cloned object will always be consistent.

Disadvantages:

  1. Cloning is hard to get right: Dilemma of Deep vs. Shallow Copy.

  2. Breaks encapsulation a little: the prototype object has to know implementation details about the class to provide the clone method, which weakens the encapsulation.

  3. Not suitable for complex inheritance hierarchy: A lot of debugging if something goes wrong, and if not properly designed, might break LSP because subclasses do not preserve the expected behaviour.

When to Use:

  1. Object creation is expensive and complex.

  2. You need many similar objects.

  3. The cloned object only differs in small ways.

  4. You want to avoid complex factories and constructors’ bloat.

  5. You can clearly define what needs to be copied and what needs to be shared. (i.e Deep vs. Shallow Copy)

More from this blog

The iamgautam03 Blog

20 posts

A growing collection of articles on software development, system design (HLD & LLD), and practical coding tips. I focus on making complex ideas simple, so developers can learn faster and build better.