Specify the kinds of objects to create using a prototypical instance, and create new objects by copying that prototype.
The Prototype pattern lets you create new objects by cloning an existing instance (the prototype) rather than building from scratch via constructors. The object itself knows how to copy itself — delegating the cloning logic inside the class rather than exposing it externally.
This is especially powerful when object creation is expensive (e.g., DB queries, network calls, heavy computation) and you just need a slightly different version of an existing object.
Suppose you have a complex object loaded from a database — parsed, validated, and populated with many fields. You need a near-identical copy but with a few tweaks. Constructing a new one from scratch is expensive and wasteful. And you can't just copy fields externally because some may be private.
Furthermore, you may not even know the exact class of the object at runtime — just its interface. You can't call new ConcreteClass() when you only have an interface reference.
clone() method. In Java, this is the Cloneable interface (or a custom one).clone() to return a deep or shallow copy of itself. Contains all the copying logic internally.new on the concrete class directly.// Prototype interface public abstract class Shape implements Cloneable { protected String color; protected int x, y; public Shape(Shape target) { // Copy constructor — used by clone() if (target != null) { this.color = target.color; this.x = target.x; this.y = target.y; } } public abstract Shape clone(); public boolean equals(Object o) { if (!(o instanceof Shape)) return false; Shape s = (Shape) o; return s.color.equals(color) && s.x == x && s.y == y; } } // Concrete Prototype 1 public class Circle extends Shape { public int radius; public Circle(Circle target) { super(target); if (target != null) this.radius = target.radius; } public Circle clone() { return new Circle(this); } } // Concrete Prototype 2 public class Rectangle extends Shape { public int width, height; public Rectangle(Rectangle target) { super(target); if (target != null) { this.width = target.width; this.height = target.height; } } public Rectangle clone() { return new Rectangle(this); } } // Prototype Registry — stores pre-built prototypes by key public class ShapeRegistry { private static Map<String, Shape> cache = new HashMap<>(); static { Circle c = new Circle(null); c.color = "red"; c.radius = 10; cache.put("red-circle", c); Rectangle r = new Rectangle(null); r.color = "blue"; r.width = 20; r.height = 30; cache.put("blue-rect", r); } public static Shape getShape(String key) { return cache.get(key).clone(); // always return a clone } } // Client usage public class Main { public static void main(String[] args) { Shape c1 = ShapeRegistry.getShape("red-circle"); Shape c2 = ShapeRegistry.getShape("red-circle"); System.out.println(c1 == c2); // false — different objects System.out.println(c1.equals(c2)); // true — same field values // Mutate the clone freely — original is unaffected ((Circle) c1).radius = 99; System.out.println(((Circle) c2).radius); // still 10 } } // ⚠ Shallow vs Deep Clone // Shallow: copies primitive fields, shares object references (default Object.clone()) // Deep: recursively copies nested objects — must implement manually public DeepObject clone() { DeepObject copy = (DeepObject) super.clone(); // shallow copy.nestedList = new ArrayList<>(this.nestedList); // deep copy list return copy; }
Object.clone() — Java's built-in shallow clone mechanism via Cloneable interfaceArrayList.clone(), HashMap.clone() — shallow copies of collections@Scope("prototype") returns a fresh clone per requestObject.create(proto) — language-level prototype-based inheritanceclone() — easy to forgetCloneable interface is considered poorly designedObject.clone() is shallow — cloned object shares references to nested mutable objects. Mutating the clone's nested object changes the original tooStackOverflowErrorCloneable, cloning it produces a second instance and breaks the Singleton guaranteeclone() — new fields are lost silently in the copysuper.clone() when nested objects existMap<Original, Clone> during cloning — if already cloned, return the existing copy instead of recursingclone() in the Singleton to throw CloneNotSupportedException, or do not implement Cloneable at allnew Circle(original)) over Object.clone() — clearer, safer, and avoids Java's broken Cloneable design// ❌ BREAKING — shallow clone shares mutable nested list public class UserProfile implements Cloneable { public List<String> roles = new ArrayList<>(); public UserProfile clone() { return (UserProfile) super.clone(); // shallow! roles list is shared } } UserProfile original = new UserProfile(); original.roles.add("admin"); UserProfile clone = original.clone(); clone.roles.add("superuser"); // also modifies original.roles! // ✅ FIX — deep clone the mutable field public UserProfile clone() { UserProfile copy = (UserProfile) super.clone(); copy.roles = new ArrayList<>(this.roles); // independent copy return copy; } // ❌ BREAKING — circular reference causes infinite recursion // Node A has reference to B; B has reference back to A // Naive deep clone: clone A → clone B → clone A → ... StackOverflow // ✅ FIX — visited map breaks the cycle public Node deepClone(Map<Node, Node> visited) { if (visited.containsKey(this)) return visited.get(this); // reuse Node copy = new Node(this.value); visited.put(this, copy); copy.next = (next != null) ? next.deepClone(visited) : null; return copy; } // ✅ Serialization-based deep clone (handles complex graphs) public static <T extends Serializable> T deepClone(T obj) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); new ObjectOutputStream(bos).writeObject(obj); return (T) new ObjectInputStream( new ByteArrayInputStream(bos.toByteArray())).readObject(); } catch (Exception e) { throw new RuntimeException(e); } }
clone().Cloneable (or a custom interface). Use a copy constructor for clean deep cloning. Optionally use a Prototype Registry (Map) to store named templates and always return clones.