Prototype Pattern
🎯 Explain Like I'm 5...
Imagine you have a really cool drawing of a superhero that you made! Instead of drawing the whole thing again from scratch (which takes forever), you can just put it on a copy machine and press the button - WHOOSH! You get an exact copy! 📄✨
🖨️ The Copy Machine Magic:
- • Original Drawing: Your awesome superhero drawing 🦸
- • Press Copy Button: Make a duplicate! 🔘
- • New Copy: Exactly the same drawing appears! 📋
Now you can color the copy differently without touching your original! 🎨
🎨 Prototype Pattern - Cloning Objects:
In programming, creating objects from scratch can be expensive (slow). The Prototype pattern lets you copy existing objects instead!
- • Clone: Make a copy of an existing object
- • Fast: Copying is usually faster than creating new
- • Customize: Change the copy without affecting the original
🚀 Why Is This Pattern Useful?
- • Avoid expensive object creation - just copy instead!
- • Create objects without knowing their exact class!
- • Reduce the number of subclasses you need!
📋 Pattern Purpose
The Prototype pattern specifies the kind of objects to create using a prototypical instance, and creates new objects by copying this prototype.
⚡ When to Use Prototype Pattern
- ✓Object creation is expensive (database queries, network calls)
- ✓You need to create many similar objects
- ✓You want to avoid creating subclasses of an object creator
- ✓You need to create objects at runtime
- ✓Objects have only a few different combinations of state
🔍 Shallow Copy vs Deep Copy
Shallow Copy:
Copies the object but shares references to nested objects. Like copying a folder but the files inside still point to the same content!
Deep Copy:
Copies everything including nested objects. Like copying a folder AND duplicating all files inside with new content!
💻 Java Implementations
1. Shallow Copy Example
Simple object cloning with primitive values and shared references
// Simple class with primitive valuesclass Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // Shallow copy constructor public Person(Person other) { this.name = other.name; // String is immutable, safe to copy reference this.age = other.age; // Primitive values are copied } // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; }}public class ShallowCopyExample { public static void main(String[] args) { // Create original person Person original = new Person("Alice", 30); System.out.println("Original: " + original); // Create shallow copy Person copy = new Person(original); System.out.println("Copy: " + copy); // Modify copy copy.setName("Bob"); copy.setAge(25); // Original remains unchanged System.out.println("\nAfter modifying copy:"); System.out.println("Original: " + original); // Still Alice, 30 System.out.println("Copy: " + copy); // Now Bob, 25 }}2. Deep Copy Example
Cloning with nested objects - everything gets copied
// Nested object - Addressclass Address { private String street; private String city; public Address(String street, String city) { this.street = street; this.city = city; } // Deep copy constructor for Address public Address(Address other) { this.street = other.street; this.city = other.city; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } @Override public String toString() { return street + ", " + city; }}// Employee with nested Address objectclass Employee { private String name; private Address address; // Nested object! public Employee(String name, Address address) { this.name = name; this.address = address; } // SHALLOW copy - shares address reference public Employee shallowCopy() { return new Employee(this.name, this.address); } // DEEP copy - creates new address object public Employee deepCopy() { return new Employee(this.name, new Address(this.address)); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } @Override public String toString() { return "Employee{name='" + name + "', address=" + address + "}"; }}public class DeepCopyExample { public static void main(String[] args) { // Create original employee Address originalAddress = new Address("123 Main St", "New York"); Employee original = new Employee("Alice", originalAddress); System.out.println("=== SHALLOW COPY ==="); Employee shallowCopy = original.shallowCopy(); // Modify shallow copy's address shallowCopy.getAddress().setCity("Los Angeles"); // Original is AFFECTED! (shares same address object) System.out.println("Original: " + original); // LA! System.out.println("Shallow: " + shallowCopy); // LA! System.out.println("\n=== DEEP COPY ==="); // Reset address originalAddress.setCity("New York"); Employee deepCopy = original.deepCopy(); // Modify deep copy's address deepCopy.getAddress().setCity("Chicago"); // Original is NOT affected (has separate address object) System.out.println("Original: " + original); // Still New York! System.out.println("Deep: " + deepCopy); // Chicago! }}3. Cloneable Interface Implementation
Standard Java cloning with the Cloneable interface
// Shape class implementing Cloneableabstract class Shape implements Cloneable { private String color; private int x; private int y; public Shape(String color, int x, int y) { this.color = color; this.x = x; this.y = y; } // Override clone() method @Override public Shape clone() { try { // Object.clone() performs shallow copy return (Shape) super.clone(); } catch (CloneNotSupportedException e) { // This should never happen since we implement Cloneable throw new AssertionError("Clone not supported", e); } } // Abstract method for drawing public abstract void draw(); // Getters and setters public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public String toString() { return "color='" + color + "', position=(" + x + "," + y + ")"; }}// Concrete shape - Circleclass Circle extends Shape { private int radius; public Circle(String color, int x, int y, int radius) { super(color, x, y); this.radius = radius; } @Override public void draw() { System.out.println("Drawing Circle: " + toString()); } @Override public String toString() { return "Circle{" + super.toString() + ", radius=" + radius + "}"; } public int getRadius() { return radius; } public void setRadius(int radius) { this.radius = radius; }}// Concrete shape - Rectangleclass Rectangle extends Shape { private int width; private int height; public Rectangle(String color, int x, int y, int width, int height) { super(color, x, y); this.width = width; this.height = height; } @Override public void draw() { System.out.println("Drawing Rectangle: " + toString()); } @Override public String toString() { return "Rectangle{" + super.toString() + ", width=" + width + ", height=" + height + "}"; }}public class CloneableExample { public static void main(String[] args) { // Create original shapes Circle circle1 = new Circle("Red", 10, 20, 5); Rectangle rect1 = new Rectangle("Blue", 30, 40, 100, 50); System.out.println("=== Original Shapes ==="); circle1.draw(); rect1.draw(); // Clone shapes Circle circle2 = (Circle) circle1.clone(); Rectangle rect2 = (Rectangle) rect1.clone(); // Modify clones circle2.setColor("Green"); circle2.setX(100); rect2.setColor("Yellow"); System.out.println("\n=== After Cloning and Modifying ==="); System.out.println("Original:"); circle1.draw(); rect1.draw(); System.out.println("\nClones:"); circle2.draw(); rect2.draw(); }}4. Copy Constructor Approach
Alternative to clone() - using a copy constructor
import java.util.ArrayList;import java.util.List;// Book classclass Book { private String title; private String author; public Book(String title, String author) { this.title = title; this.author = author; } // Copy constructor for Book public Book(Book other) { this.title = other.title; this.author = other.author; } @Override public String toString() { return "'" + title + "' by " + author; }}// Library class with collection of booksclass Library { private String name; private List<Book> books; public Library(String name) { this.name = name; this.books = new ArrayList<>(); } // Copy constructor - performs deep copy of books list public Library(Library other) { this.name = other.name; // Deep copy: create new list and clone each book this.books = new ArrayList<>(); for (Book book : other.books) { this.books.add(new Book(book)); // Clone each book } } public void addBook(Book book) { books.add(book); } public String getName() { return name; } public List<Book> getBooks() { return books; } public void displayBooks() { System.out.println("Library: " + name); for (Book book : books) { System.out.println(" - " + book); } }}public class CopyConstructorExample { public static void main(String[] args) { // Create original library Library library1 = new Library("Central Library"); library1.addBook(new Book("1984", "George Orwell")); library1.addBook(new Book("To Kill a Mockingbird", "Harper Lee")); System.out.println("=== Original Library ==="); library1.displayBooks(); // Create copy using copy constructor Library library2 = new Library(library1); // Add a book to the copy library2.addBook(new Book("The Great Gatsby", "F. Scott Fitzgerald")); System.out.println("\n=== After Adding Book to Copy ==="); System.out.println("Original Library:"); library1.displayBooks(); // Still has 2 books System.out.println("\nCopied Library:"); library2.displayBooks(); // Now has 3 books System.out.println("\n✓ Original library is NOT affected by changes to copy!"); }}🌍 Real-World Examples
- 📄Document Templates: Copy a template document instead of creating from scratch
- 🎮Game Objects: Clone enemy characters with similar properties
- ⚙️Configuration Cloning: Duplicate system configurations for different environments
- 🎨Shape/Graphics Editors: Copy drawn shapes to paste elsewhere
- 💾Database Records: Create similar records by copying existing ones
✅ Benefits
- ✅Performance: Faster than creating complex objects from scratch
- ✅Flexibility: Add/remove objects at runtime
- ✅Reduced Subclassing: Avoid creating many similar classes
- ✅Encapsulation: Hide complex creation logic
⚠️ Pitfalls to Avoid
- ⚠️Shallow vs Deep Copy Confusion: Know which one you need!
- ⚠️Cloneable Issues: clone() is protected and throws checked exceptions
- ⚠️Circular References: Deep copying can cause infinite loops
- ⚠️Immutable Objects: Some objects shouldn't be cloned (like singletons)
- ⚠️Missing clone() Override: Subclasses must override clone() properly
🔑 Key Points to Remember
- 1️⃣Prototype creates new objects by copying existing ones (cloning)
- 2️⃣Shallow copy shares references, deep copy duplicates everything
- 3️⃣Implement Cloneable interface and override clone() method
- 4️⃣Copy constructors are often safer than clone()
- 5️⃣Use when object creation is expensive or complex
- 6️⃣Be careful with mutable objects in shallow copies
💪 Practice Scenarios
- • Create a game character system where you clone enemy templates
- • Build a document system with template cloning
- • Implement a shape cloning system for a graphics editor
- • Create a configuration cloner with deep copy for nested settings
- • Design a prototype registry that manages and clones different prototypes