Prototype

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?

Creating by Cloning

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.

Shallow vs. Deep Copy

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.

  • Shallow Copy: This is the default behavior in many languages. It copies the top-level fields of an object. If a field contains a primitive type (like an 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.
  • Deep Copy: This is a more comprehensive copy. It copies all fields and recursively clones any objects referenced by those fields. The end result is that the original and the clone are completely independent; a change in one will not affect the other.

Example: A Shape Registry

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.

Interview Focus: Analysis and Trade-offs

Benefits:

  • Performance: The primary advantage. If object creation is more expensive than cloning, this pattern provides a significant performance boost.
  • Simplifies Creation: It hides the complex details of object initialization from the client. The client simply asks for a clone.
  • Dynamic Configuration: You can create new "prototypes" at runtime, configure them, and add them to a registry, providing a very dynamic way to add new object variations to your system.

Drawbacks:

  • Complexity of Cloning: The biggest drawback is that implementing 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.

How Prototype Relates to Other Concepts

  • An Alternative to Factory Patterns: Instead of using a 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.
  • Relationship to Caching: The core idea of keeping a fully-initialized, ready-to-use object is closely related to the concept of caching or memoization. The prototype registry is essentially a cache of prototypical objects.
  • Common Use Cases:
    • Game Development: Mass-producing game entities (like enemies, trees, or projectiles) from a pre-configured prototype is a very common and efficient technique.
    • Data-Intensive Applications: When an object's state is determined by a costly database query, you can query once to create a prototype, and then clone it for subsequent processing to avoid hitting the database repeatedly.
    • UI Components: Creating copies of complex UI elements that have already been configured.