Prototype Pattern
The Prototype Pattern is used to create duplicate objects while ensuring performance. This type of design pattern falls under the category of creational patterns, providing an optimal way to create objects.
This pattern implements a prototype interface that is used to clone the current object. When the cost of creating an object directly is high, this pattern is used. For example, an object needs to be created after a costly database operation. We can cache the object and return its clone on the next request, updating the database when necessary, thereby reducing database calls.
Introduction
Intent: Specify the kinds of objects to create using a prototypical instance and create new objects by copying this prototype.
Main Solution: Establish and delete prototypes at runtime.
When to Use:
- When a system should be independent of how its products are created, composed, and represented.
- When the class to be instantiated is specified at runtime, for example, by dynamic loading.
- To avoid building a parallel factory class hierarchy.
- When an instance of a class can only be one of several different combinations of states. It may be more convenient to install a corresponding number of prototypes and clone them than to instantiate the class manually with the appropriate state each time.
How to Solve: Quickly generate instances identical to the prototype object using an existing prototype object.
Key Code:
- Implement cloning, in Java by implementing the Cloneable interface and overriding the clone() method, in .NET by using the Object class's MemberwiseClone() method for shallow copy or by serialization for deep copy.
- The Prototype Pattern also isolates the coupling between the class object's users and the concrete types (volatile classes) and requires these "volatile classes" to have a stable interface.
Application Examples:
- Cell division.
- The Object clone() method in Java.
Advantages:
- Improved performance.
- Bypasses the constraints of the constructor.
Disadvantages:
- Implementing the clone method requires a comprehensive consideration of the class's functionality, which is not difficult for a new class but may not be easy for an existing class, especially when a class references indirectly serializable objects or objects with cyclic structures.
- Must implement the Cloneable interface.
Usage Scenarios:
- Resource optimization scenarios.
- Class initialization requires a large amount of resources, including data, hardware resources, etc.
- Scenarios with performance and security requirements.
- If creating an object through new requires very cumbersome data preparation or access permissions, the Prototype Pattern can be used.
- Scenarios where an object has multiple modifiers.
- When an object needs to be provided to other objects for access, and various callers may need to modify its value, the Prototype Pattern can be considered to copy multiple objects for callers to use.
- In actual projects, the Prototype Pattern rarely appears alone but often with the Factory Method Pattern. An object is created via clone and then provided to the caller by the factory method. The Prototype Pattern is already integrated into Java, and it can be used readily.
Precautions: Unlike constructing a new object by instantiating a class, the Prototype Pattern generates a new object by copying an existing one. Shallow copy implements Cloneable and overrides, deep copy implements Serializable and reads the binary stream.
Implementation
We will create an abstract class Shape and concrete classes that extend Shape. Next, we define the class ShapeCache, which stores shape objects in a Hashtable and returns their clones upon request.
The PrototypePatternDemo class uses the ShapeCache class to get Shape objects.
Step 1
Create an abstract class that implements the Cloneable interface.
Shape.java
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;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
Step 2
Create concrete classes that extend the above abstract class.
Rectangle.java
public class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
Square.java
public class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
Circle.java
public class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
Step 3
Create a class that retrieves entity classes from the database and stores them in a Hashtable.
ShapeCache.java
import java.util.Hashtable;
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// For each shape run database query and create shape
// shapeMap.put(shapeKey, shape);
// For example, we are adding three shapes
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);
}
}
Step 4
PrototypePatternDemo uses ShapeCache class to get clones of shapes stored in the Hashtable.
PrototypePatternDemo.java
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.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());
}
}
Step 5
Execute the program, output results:
Shape : Circle
Shape : Square
Shape : Rectangle