In this article of Java design pattern, we will look at the Prototype Design Pattern. The Prototype pattern is part of the Creational Design Pattern.
Prototype Design Pattern
The prototype design pattern refers to duplicating an item while considering performance. We classify this design pattern as a creational pattern, since it gives one of the most effective ways to construct an object.
This technique entails creating a prototype interface that instructs the current object to be cloned. When creating an object directly is expensive, this pattern is employed. For instance, an object is to be constructed following a time-consuming database operation. We can cache the object, return its clone on the next request, and update the database as needed, resulting in fewer database calls.
Object class’s
clone()
method is an example of a Prototype Design Pattern
We use prototypes design patterns in real life to conduct many tests before to mass production of a product. Prototypes do not engage in actual production in this situation, instead of performing a passive role.
1. How to Implement Prototypes Pattern
Before we get in the code, it’s always important that we understand what are the steps to implement the prototypes design pattern. Let’s look at the distinct steps.
- Make a prototype interface and add the clone function to it.
- An alternate function
Object()
for a prototype class that accepts an object of that class as an argument must be defined. The functionObject()
must copy the values of all class fields into the newly generated instance from the object. If you’re updating a subclass, you must use the parent functionObject()
to delegate the cloning of its private fields to the super-class. - The cloning method usually comprises only one line: calling a new operator using the constructor’s prototype version. Each class must override the cloning method and use its class name with the new operator. Otherwise, the cloning method may create a child object.
This is how the overall relation look like:
Let’s look at some important component of the above UML
- interface -> The cloning techniques are declared in the interface. It’s usually only a single clone method.
- Concrete Class -> The cloning method is implemented by the Concrete Prototype class. Besides copying the original object’s data to the clone, this method may also handle some edge cases of the cloning process related to cloning linked objects, untangling recursive dependencies, and so on, besides copying the original object’s data to the clone.
- Client Class -> The client class will use the Interface to create/get the copy of Concrete class.
2. Prototype Pattern In Java
We have understood the basic of prototypes design pattern. Let’s implement the same using Java. We’ll create an abstract class called Shape
, as well as concrete classes (Circle
, Rectangle
, Square
) that extend it. As a next step, the ShapeCache
class is defined, which saves shape objects in a HashTable
and returns their clone when asked.
Our demo class, PrototypPatternDemo
, will gain a Shape object using the ShapeCache
class and return the clone of the concrete classes. This is how our Java code will look like:
package javadevjournal.design.creational.prototype;
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType() {
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
* Use Object class's Clone() method to do the cloning.
* @return
*/
public Object clone() {
Object cloneObject = null;
try {
cloneObject = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("cloning failed");
e.printStackTrace();
}
return cloneObject;
}
}
Make concrete classes that extend the above abstract class
package javadevjournal.design.creational.prototype;
public class Rectangle extends Shape {
public Rectangle() {
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Rectangle's draw() method.");
}
}
//Square.java
package javadevjournal.design.creational.prototype;
public class Square extends Shape {
public Square() {
type = "Square";
}
@Override
public void draw() {
System.out.println("Square's draw() method.");
}
}
//Circle.java
package javadevjournal.design.creational.prototype;
public class Circle extends Shape {
public Circle() {
type = "Circle";
}
@Override
public void draw() {
System.out.println("Circle's draw() method.");
}
}
Create a class that retrieves concrete classes from the database and stores them in a Hashtable:
package javadevjournal.design.creational.prototype;
import java.util.HashMap;
import java.util.Map;
public class ShapeCache {
private static Map < String, Shape > shapeMap = new HashMap < String, Shape > ();
/**
* Return cloned object of Shape class implementing classes.
* @param shapeId
* @return
*/
public static Shape getShape(String shapeId) {
Shape toBeClonedShape = shapeMap.get(shapeId);
return (Shape) toBeClonedShape.clone();
}
/**
* In real-world applications, the loading of details will be from a Database.
* We are using a HaspMap to demonstrate the same behavior.
*/
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(), circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(), square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(), rectangle);
}
}
Finally, demo class:
package javadevjournal.design.creational.prototype;
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape1 = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape1.getType());
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
System.out.println("Shape : " + clonedShape3.getType());
}
}
Here is the output of of code:
If you are curious, this is how all these classes and interface are linked in our Prototype Design Pattern.
3. Advantages of Prototype Pattern
Let’s see some advantages of using this pattern
- Adding and deleting products in the middle of a game–By registering a prototype instance with the client, you may easily integrate a new concrete product class into a system. Because a client can install and uninstall prototypes during runtime, this pattern is a little more flexible than other creational patterns.
- Creating new objects by changing their values–Object composition, rather than introducing new classes, allows you to design new behavior in highly dynamic systems by specifying values for an object’s variables.
- Creating new objects by changing their structure – Many apps create things out of parts and subparts. Such apps frequently allow you to create sophisticated, user-defined structures to reuse a single sub-circuit.
- Many designs start by using Factory Method (less complicated and more customizable via sub classes) and grow toward Abstract Factory, Prototype, or Builder (more flexible, but more complicated).
3.1. Disadvantages
- For a project with few objects and/or no underlying emphasis on the extension of prototype chains, this is overkill.
- It also keeps certain product classes hidden from the client.
- When the classes under consideration already exist, each subclass of Prototype must implement the
clone(
) function, which can be complex. Also, when their internals involve objects that don’t permit copying or have circular references, implementingclone()
can be challenging.
3.2. When to use Prototype Design Pattern
- During the runtime, when the concrete classes are instantiated.
- When the expense of producing a product is prohibitively high or complex.
- When you wish to keep your applications, class count to a bare minimum.
- When the client application doesn’t need to know about object creation or representation.
Summary
In this post, we talked about the Prototype Design Pattern. We saw some of the real-world examples along with what are some advantages of using this pattern. We also saw a Java implementation for this design pattern. You can always check our GitHub repository for the latest source code.