Prototype Pattern: The Polymorphism Trap

Why is copying objects harder than it looks? Explore the 'If-Else' nightmare of manual cloning and why the Prototype Pattern is the only way to handle polymorphic copies.

April 22, 20243 min read1 / 2

Imagine a client class that has a reference to a Student object:

Java
Student st1 = getStudent(); // This could return a Student or any of its subclasses Student st2 = // How do we create a perfect copy of st1?

Why do we even need a copy? The most common reason is to modify the object for a specific task without affecting the original "source of truth."

Approach 1: Manual Attribute Copying

The most naive approach is to create a new instance in the client and copy every attribute one by one.

Why Manual Copying Fails

  1. Private Attributes: The client cannot access st1.name directly. Even if getters exist, it's not guaranteed that every internal state variable is exposed.
  2. Implementation Leakage: The client now knows exactly how many attributes a Student has. This leads to Tight Coupling.
  3. Abstraction Violation: Abstraction means hiding internal details. Manual copying forces the client to peek "under the hood."

Approach 2: The Copy Constructor

To solve the private attribute issue, we could use a Copy Constructor:

Java
public class Student { private String name; private int age; // Copy Constructor public Student(Student other) { this.name = other.name; this.age = other.age; } }

The client calls: Student st2 = new Student(st1);

The Critical Flaw: The Polymorphism Trap

While the copy constructor handles private attributes, it fails miserably when Inheritance is involved. Imagine Student has a child class called IntelligentStudent.

Java
Student st1 = new IntelligentStudent(); // Polymorphism Student st2 = new Student(st1);
  • st1 is actually an IntelligentStudent on the heap.
  • But the client calls new Student(st1).
  • Result: st2 is a plain Student object. We have lost all the data of the IntelligentStudent!

The "If-Else" Nightmare (OCP Violation)

To fix this, the client would have to check the actual type at runtime:

Java
Student st2; if (st1 instanceof IntelligentStudent) { st2 = new IntelligentStudent((IntelligentStudent) st1); } else { st2 = new Student(st1); }

This violates the Open-Closed Principle. Every time you add a new subclass, you must update every client that performs a copy.

The Solution: "I Know Myself Best"

If a client cannot copy an object safely, the class itself should do it. The object knows its own private fields and its own runtime type.

We introduce a method, traditionally called copy() or clone(). This is the heart of the Prototype Pattern.

Java
public class Student { public Student copy() { return new Student(this); // Calls the internal copy constructor } } // Client Code (Simple & Polymorphic) Student st2 = st1.copy();

Java vs. JavaScript: Cloning Realities

1. In Java (Multi-threaded & Structured)

Java has a built-in Cloneable interface, but it is widely considered "broken" because it doesn't provide a clone() method (it's just a marker). The approach we used above--Copy Method + Copy Constructor--is the industry standard.

2. In JavaScript / TypeScript

In the single-threaded world of JS, we often use the Spread Operator for shallow copies:

JavaScript
const st2 = { ...st1 };

The Problem: Spread only does a Shallow Copy. If st1 has a nested object (like an Address), both st1 and st2 will point to the same address object.

For deep, polymorphic copies in JS/TS classes, the Prototype Pattern is still the superior choice.

In the next part, we will learn how to manage these prototypes using the Registry Pattern.