Home/Design Patterns/Prototype Pattern

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

ShallowCopyExample.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Simple class with primitive values
class 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

DeepCopyExample.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Nested object - Address
class 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 object
class 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

CloneableExample.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Shape class implementing Cloneable
abstract 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 - Circle
class 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 - Rectangle
class 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

CopyConstructorExample.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import java.util.ArrayList;
import java.util.List;
// Book class
class 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 books
class 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