Fundamentals 18 min read

Prototype Pattern: The Final Creational Design Pattern Explained

The article explains the Prototype design pattern, showing how cloning existing objects can replace costly object construction, details Java’s Cloneable and clone() mechanisms, the pitfalls of shallow copying, and various deep‑copy techniques such as manual recursion, serialization, and library utilities.

Dabaoshi
Dabaoshi
Dabaoshi
Prototype Pattern: The Final Creational Design Pattern Explained

1. From "Reorder" to Clone vs. new

When a user clicks "Reorder" the system needs a new order that is almost identical to a previous one. Using new requires copying each field manually, which is verbose, error‑prone, and forces the caller to know every field of Order. If the Order class later adds a field, all manual copy code must be updated, violating encapsulation.

Order newOrder = new Order();
newOrder.setUserId(oldOrder.getUserId());
newOrder.setAddress(oldOrder.getAddress());
newOrder.setItems(oldOrder.getItems());
// ... many fields
newOrder.setOrderNo(generateNewNo()); // only this changes
newOrder.setCreateTime(now());

Cloning avoids these problems by letting the object copy itself, keeping the caller unaware of its internal structure.

2. Prototype Pattern Definition

The Prototype pattern uses an existing instance as a prototype and creates new objects by cloning it instead of invoking new and re‑initialising. The core operation is the clone() method.

Abstract Prototype : declares clone().

Concrete Prototype : implements clone() and returns a copy of itself.

public interface Prototype {
    Prototype clone(); // "I can copy myself"
}

public class Order implements Prototype {
    private long userId;
    private String address;
    private List<Item> items;
    // ... other fields
    @Override
    public Order clone() {
        Order copy = new Order();
        copy.userId = this.userId;
        copy.address = this.address;
        copy.items = this.items; // ← shallow copy, problematic
        // ... copy all fields
        return copy;
    }
}

Now a new order can be created with a single line:

Order newOrder = oldOrder.clone();
newOrder.setOrderNo(generateNewNo());
newOrder.setCreateTime(now());

3. Java’s Native Support: Cloneable & clone()

Java provides a built‑in cloning mechanism via Object.clone(), which performs a field‑by‑field copy. To use it, a class must:

Implement the marker interface Cloneable.

Override clone(), make it public, and delegate to super.clone().

public class Order implements Cloneable {
    @Override
    public Order clone() {
        try {
            return (Order) super.clone(); // shallow copy
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

Issues with this design (as highlighted in *Effective Java*) include: Cloneable is an empty marker interface, separating the “switch” from the actual cloning logic. clone() is protected in Object, forcing a public override.

The method throws the checked CloneNotSupportedException, requiring boiler‑plate try‑catch even when the exception cannot occur.

Because of these quirks, Effective Java recommends using copy constructors or static factory methods instead of Cloneable.

4. Shallow‑Copy Pitfall

Object.clone()

performs a shallow copy: primitive fields are duplicated, but reference‑type fields copy only the reference. Consequently, the cloned and original objects share the same internal mutable objects.

Order newOrder = oldOrder.clone(); // shallow copy
newOrder.getItems().add(new Item("Gift")); // modifies both orders
System.out.println(oldOrder.getItems()); // shows the gift as well

The line copy.items = this.items; is the root cause; both orders now reference the same List<Item>. This bug is hard to detect because only mutable reference fields exhibit the issue.

5. Deep‑Copy Implementations

When an object contains mutable references, a deep copy is required. Three common approaches are:

Manual recursive copy : Override clone() and explicitly clone each mutable field.

@Override
public Order clone() {
    try {
        Order copy = (Order) super.clone(); // shallow copy first
        copy.items = new ArrayList<>(this.items); // copy the list
        // if Item is mutable, clone each element as well
        return copy;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

Pros: fast and controllable. Cons: error‑prone for deep object graphs.

Serialization / deserialization : Write the object to a byte stream and read it back, which reconstructs a completely independent object.

public Order deepCopy() {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
        oos.writeObject(this);
    }
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    try (ObjectInputStream ois = new ObjectInputStream(bis)) {
        return (Order) ois.readObject();
    }
    // exception handling omitted for brevity
}

Pros: handles arbitrarily deep structures automatically. Cons: requires all classes to implement Serializable and incurs noticeable performance overhead.

Utility libraries : e.g., Apache Commons SerializationUtils.clone() or bean‑copy tools, which wrap the serialization approach.

Choosing a method depends on object complexity and performance requirements: simple shallow structures can stay with a copy constructor; complex, deeply nested objects benefit from serialization‑based deep copy.

6. Prototype Registry

A Prototype Registry pre‑creates a set of standard prototype objects and stores them in a map. Clients request a clone by key, avoiding repeated expensive initialisation.

public class OrderRegistry {
    private static final Map<String, Order> registry = new HashMap<>();
    static {
        registry.put("normal", createNormalPrototype());
        registry.put("presale", createPresalePrototype());
        registry.put("group", createGroupPrototype());
    }
    public static Order create(String type) {
        return registry.get(type).clone();
    }
}

This is useful when the default configuration is costly to build but shared across many instances.

7. Real‑World Footprint

Object.clone()

: default shallow‑copy mechanism. ArrayList.clone() and HashMap.clone(): also shallow; the internal array or map entries are shared. Arrays.copyOf(): shallow copy of array references.

Spring’s @Scope("prototype") creates a new instance each request, which is *not* the GoF Prototype pattern (it does not clone an existing object).

The article concludes that the Prototype pattern replaces costly construction with cloning, but the decisive question is always whether a shallow or deep copy is needed; overlooking this leads to hidden bugs.

8. Summary

Prototype pattern uses cloning to create objects that are expensive to construct, delegating the copy responsibility to the object itself. Java’s Cloneable and Object.clone() are notoriously awkward, prompting many developers to prefer copy constructors. The essential decision is shallow versus deep copy: shallow copy shares mutable internal references and can cause subtle bugs, while deep copy creates fully independent objects via manual recursion, serialization, or helper libraries. A Prototype Registry can further optimise scenarios with costly default configurations. Use the pattern only when object creation is expensive and a full state copy is required.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaDeep CopyShallow CopyPrototype PatternCloneable
Dabaoshi
Written by

Dabaoshi

Practical utilities

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.