Creational Design Patterns
Imagine you are developing a large-scale data processing application. You need to work with a DataRecord object that is very expensive to create. Its constructor needs to query a database, read a large configuration file, and perform several complex calculations to establish its initial state.
public class DataRecord {
public DataRecord() {
// 1. Expensive database query...
// 2. Expensive file I/O to read config...
// 3. Expensive initial calculations...
System.out.println("Creating a very expensive DataRecord object...");
}
}
Now, what if your application needs to create hundreds or thousands of these DataRecord objects, many of which are only slight variations of each other? Creating each one from scratch using new DataRecord() would be incredibly slow and inefficient, leading to significant performance bottlenecks. How can we produce new objects without incurring this high creation cost every single time?
The Prototype is a creational pattern that lets you create new objects by copying an existing, fully initialized object, known as the "prototype."
Its official intent is to:
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
Instead of a class dictating how to build an object from scratch, the Prototype pattern relies on the concept of cloning. You create one master object (the prototype), and whenever you need a new object, you simply ask the prototype to make a copy of itself.
The core of the Prototype pattern is a clone() method. The most important topic to discuss about this method in an interview is the difference between a shallow and a deep copy.
int or double), its value is copied. However, if a field contains a reference to another object, only the reference is copied, not the object itself. This means both the original and the cloned object will point to the exact same internal object.Let's design a graphics application where we can create copies of pre-configured shapes. We'll use Java's built-in Cloneable interface.
Step 1: The Prototype Interface (and Abstract Class)
We'll create an abstract Shape class that can be cloned.
public abstract class Shape implements Cloneable {
public String color;
// The clone method
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone(); // Performs a shallow copy
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
Step 2: Concrete Prototypes
public class Circle extends Shape {
public int radius;
// ... constructor, etc.
}
public class Rectangle extends Shape {
public int width;
public int height;
// ... constructor, etc.
}
Step 3: The Client and Prototype Registry
The client can now use these prototypes to create new objects without using the new keyword and their constructors. A "Prototype Registry" is a common way to manage these prototypes.
import java.util.Hashtable;
public class ShapeRegistry {
private static Hashtable<String, Shape> shapeMap = new Hashtable<>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
// The key part: we return a CLONE, not the original instance.
return (Shape) cachedShape.clone();
}
// Pre-load the registry with prototype instances
public static void loadCache() {
Circle circle = new Circle();
circle.color = "Red";
shapeMap.put("red_circle", circle);
Rectangle rectangle = new Rectangle();
rectangle.color = "Blue";
shapeMap.put("blue_rectangle", rectangle);
}
}
// Client Code:
ShapeRegistry.loadCache();
Shape clonedCircle = ShapeRegistry.getShape("red_circle");
System.out.println("Cloned shape is a " + clonedCircle.color + " circle.");
Shape clonedRectangle = ShapeRegistry.getShape("blue_rectangle");
System.out.println("Cloned shape is a " + clonedRectangle.color + " rectangle.");
The expensive creation of the initial red circle and blue rectangle happens only once. All subsequent shapes are created via a cheap cloning operation.
Benefits:
Drawbacks:
clone() correctly can be very difficult. Managing circular references and deciding between a shallow vs. deep copy for every field can be complex and lead to subtle bugs if not handled carefully.Factory Method and a large class hierarchy to produce different object configurations, you can use the Prototype pattern with a few pre-configured prototype objects. This can lead to fewer classes in your system.