Java programming language logo

Prototype Design Pattern

The Prototype pattern creates new object instances by cloning an existing one. This pattern should be used when object instantiation involves costly operations like database querying, and several instances needed from that object.

The Prototype pattern creates a new object instance and caches it. Every time when it receives a request for that object, it creates a clone from the cached one and returns with the clone. The cached instance never exposed to the clients, so it's never changing and always suitable for initializing a new instance.

Prototype is a creational pattern.

Example

Let’s see a Java example for the Prototype pattern. In the next example, we are creating several watercraft instances in an effective way.

The Watercraft class has three fields. The type can be motorboat or yacht. The color and length values come from a database.

Its constructor is private, so it cannot be called directly by the clients. A new instance can be created by the getInstance method, which runs database queries. As you can see, creating a new instance of the below class involves costly operations.

The Watercraft class implements the Cloneable interface, so it has a clone method. The implementation of this method is very simple because the Watercraft class does not have any mutable field.


package com.programcodex.designpatterns.prototype;

public class Watercraft implements Cloneable {

    private String type;
    private String color;
    private int length;

    private Watercraft(String type, String color, int length) {
        System.out.println("Watercraft constructor running...");
        this.type = type;
        this.color = color;
        this.length = length;
    }

    public static Watercraft getInstance(String watercraftType) {
        String color = Database.gueryColor(watercraftType);
        int length = Database.gueryLength(watercraftType);
        return new Watercraft(watercraftType, color, length);
    }

    @Override
    protected Watercraft clone() throws CloneNotSupportedException {
        return (Watercraft) super.clone();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("\nWatercraft type: ").append(type);
        sb.append(", color: ").append(color);
        sb.append(", length: ").append(length);
        sb.append(" m, hashCode: ").append(hashCode());
        return sb.toString();
    }
}

The Database class is a mock database. It tries to represent the costly operations in this example. Instead of querying from a real database, it gets the data from two HashMaps.


package com.programcodex.designpatterns.prototype;

import java.util.HashMap;
import java.util.Map;

public class Database {
    
    private static final Map<String, String> colors;
    private static final Map<String, Integer> lengths;
    
    static {
        colors = new HashMap<>();
        colors.put("motorboat", "gray");
        colors.put("yacht", "white");
        
        lengths = new HashMap<>();
        lengths.put("motorboat", 5);
        lengths.put("yacht", 22);        
    }

    public static String gueryColor(String watercraftType) {
        System.out.println("\nDatabase query running...");
        return colors.get(watercraftType);
    }

    public static int gueryLength(String watercraftType) {
        return lengths.get(watercraftType);
    }
}

When we need a new instance of the Watercraft class, we will require one from the WatercraftProducer class. This class obtains a cache of Watercrafts. When it receives a request for a new watercraft, it gets the instance from the cache, clone it, and returns with the clone.

If the required watercraft doesn’t exists in the cache, it creates a new instance for it and puts it into the cache. After that, it can respond to the request.


package com.programcodex.designpatterns.prototype;

import java.util.HashMap;
import java.util.Map;

public class WatercraftProducer {

    private Map<String, Watercraft> cache = new HashMap<>();
    
    public Watercraft get(String watercraftType) {
        Watercraft watercraft = cache.get(watercraftType);

        if (watercraft == null) {
            watercraft = Watercraft.getInstance(watercraftType);
            cache.put(watercraftType, watercraft);
        }

        try {
            return watercraft.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

And we are done. It’s time to run these things.

First, a motorboat and a yacht instances are created. Because these instances don’t exist in the cache, database queries are running.

After that, another motorboat and yacht instances are created, but this time there’s no need for database querying.

Every object has a unique hash code because they are different instances. By default, the hashCode method returns an integer that represents the internal memory address of the object.


package com.programcodex.designpatterns.prototype;

public class TestPrototype {
    
    private static final String MOTORBOAT = "motorboat";
    private static final String YACHT = "yacht";

    public static void main(String[] args) {
        WatercraftProducer producer = new WatercraftProducer();
        
        Watercraft m1 = producer.get(MOTORBOAT);        
        System.out.println(m1);
        
        Watercraft y1 = producer.get(YACHT);        
        System.out.println(y1);

        Watercraft m2 = producer.get(MOTORBOAT);        
        System.out.println(m2);
        
        Watercraft y2 = producer.get(YACHT);        
        System.out.println(y2);
    }
}

The output of the above test class:

The Prototype Design Pattern