Creational Design Pattern: Prototype

The Prototype Design Pattern is a creational pattern that lets us create new objects by cloning existing ones instead of instantiating them from scratch.

Photo by Szylemon Fikcyjny on Unsplash

This article is part of a series exploring design patterns using the Java programming language. The goal of this series is to help readers develop a solid understanding of design patterns while also sharing real-world examples from actual codebases that make use of these patterns.

In this article, we’ll be discussing the Prototype pattern.

Symbols for better navigation:

🤔 Hypothetical Scenario/Imagination

⚠️ Warning

👉 Point to note

📝 Common Understanding

Prototype Pattern

The Prototype Design Pattern allows the creation of new objects by cloning an existing prototype.

A prototype acts like a mold or template that can be reused to generate multiple objects.

Consider a scenario where we need to create several objects of the same type, but each with a different state. A common example of this is UI elements.

🤔 For instance, suppose we want to create labels in a form UI with predefined properties such as span, font size, and style. It would be inefficient for a developer to reinitialize such elements from scratch every time. Instead, they could simply use a prototype of the label element and create a new Label instance whenever needed.

Take a look at this example.

Java
public class TestPrototype {
    public static void main(String[] args) {
        Label l = new Label("My Label", 3, true);
        Label l2 = l.clone(); // prototyping
        l2.setText("My Label 2");
    }
}

In this example, we are performing a shallow clone of the Label object to create a new label with the same properties/state. This demonstrates the basic working of the Prototype Pattern, which is creating new objects by copying existing ones instead of constructing them from scratch.

👉 However, the Prototype Pattern goes beyond simple cloning. A common enhancement is to maintain a registry of prototype objects that can be reused to create new instances.

For example, take a look at this code.

Java
public class TestPrototype {
    public static void main(String[] args) {
        Registry reg = Registry.getRegistry();
        Label myNewLabel = reg.createItem("label"); // getting prototypes from registry
    }
}

Here, instead of manually cloning objects, we retrieve prototypes from a centralized registry and use them to create new objects.

📈 Creation of similar types of objects via prototype design patterns then has multiple advantages:

  1. Avoids costly instantiation: Objects can be created more efficiently by cloning rather than repeatedly calling constructors, which saves developer effort.
  2. Reduces subclassing: New types of objects can be stored in the registry as prototypes and reused without the need to create separate subclasses.
  3. Centralized prototype registry: All prototypes are maintained in one place, making object creation more organized and easier to manage.
  4. Pre-established contract defaults: This means the developer doesn’t have to fill default property values on object creation.

Recipe to cook Prototype Pattern for objects

Utilizing the Prototype Pattern for object creation is simple. The idea is to make our objects cloneable and keep a registry of prototype instances that can be reused across the system.

🤔 For example, let’s say I have a Label object as follows.

Java
public class Label {
    private String text;
    private int span;
    private boolean visible;
    private Props props;

    public Label(String text, int span, boolean visible, Props props) {
        this.text = text;
        this.span = span;
        this.visible = visible;
        this.props = props;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public int getSpan() {
        return span;
    }

    public void setSpan(int span) {
        this.span = span;
    }

    public boolean isVisible() {
        return visible;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    public Props getProps() {
        return props;
    }

    public void setProps(Props props) {
        this.props = props;
    }
}

To convert this into a prototype, I’ll first make it cloneable as follows.

Java
public class Label implements Cloneable {
    private String text;
    private int span;
    private boolean visible;
    private Props props;

    public Label(String text, int span, boolean visible, Props props) {
        this.text = text;
        this.span = span;
        this.visible = visible;
        this.props = props;
    }

    @Override
    protected Label clone() {
        try {
            Label cloned = (Label) super.clone(); // shallow clone
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Cloning is not supported", e);
        }
    }

    // Getters and setters...
}

⚠️ But there’s one issue with this code, if you observe, the props property of the Label class is also an object. And when we try to override the clone method, it only creates a shallow copy of the Label object.

Shallow copy means that only the immediate property values, viz., text, span and visible, are copied. However, when it comes to props, a new object of type Prop will be initialized with null values.

A more elaborate explanation of this means that, suppose the Prop class had one property named name, then instead of copying the actual value in the cloned object of the Label, it will create a new prop property with an initial name value.

👉 To prevent this, we have to perform a deep cloning of the Label object by overriding the Label class clone method as follows.

Java
protected Object clone() throws CloneNotSupportedException {
    try {
        Label clonedLabel = (Label) super.clone(); // shallow cloning
        clonedLabel.setProps(this.props.clone()); // props should be cloneable!
        return clonedLabel();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return null;
}

This method will now create a deep copy of the Label object. ⚠️ However, we should note that the Props class too should be a cloneable and must have overridden the clone method in its own class.

Now, the last step in the prototype is to create a registry of the prototypes so that they can be reused globally anywhere in our code.

Java
public class Registry {
    private Map<String, Label> items = new HashMap<>();

    private Registry() {
        loadItems();
    }

    private static class RegistryHolder {
        private static final Registry INSTANCE = new Registry();
    }

    public static Registry getRegistry() {
        return RegistryHolder.INSTANCE;
    }

    private void loadItems() {
        items.put("label", new Label("Default Label", 3, true, new Props("default-prop")));
    }

    public Label createItem(String itemName) {
        Label prototype = items.get(itemName);
        return prototype != null ? prototype.clone() : null;
    }
}

Finally, let’s bring all of this together in our code.

Java
public class TestPrototype {
    public static void main(String[] args) {
        Registry registry = Registry.getRegistry();

        Label l1 = registry.createItem("label"); // registry access
        l1.setText("First Label");

        Label l2 = registry.createItem("label");
        l2.setText("Second Label");
        l2.getProps().setName("custom-prop"); // set values
}

Disadvantages of Using Prototypes

  • Collections of Prototypes in a registry resemble that of a framework rather than a pattern.
  • Shallow and Deep Cloning both require manual implementation.

Up Next in the Series

Factory Design Pattern

Subscribe to my newsletter today!

Share it on

Leave a Reply

Your email address will not be published. Required fields are marked *