The Prototype Pattern in Java is used to create new objects by cloning an existing object, known as the prototype, rather than creating new instances from scratch. This pattern is useful when creating objects is costly or complex, and the new objects are similar to existing ones. Let’s implement a Java program that demonstrates creating new customer accounts by copying an existing template account, including default settings and initial balances.
Key Components
- Prototype: An interface or abstract class declaring a clone() method for copying itself.
- Concrete Prototype: A class implementing the clone() method to return a copy of itself.
- Client: Uses the prototype to create new objects by cloning.
How It Works
- The prototype object serves as a template.
- The client calls the clone() method on the prototype to create a new object.
- The cloned object is a copy (shallow or deep, depending on implementation) of the prototype.
- The client can modify the cloned object without affecting the original prototype.
Types of Cloning
- Shallow Copy: Copies the object’s fields, but references to nested objects are shared.
- Deep Copy: Recursively copies the object and all nested objects, ensuring complete independence.
Sample Implementation
// Prototype Interface
interface Prototype extends Cloneable {
Prototype clone();
}
// Concrete Prototype
class Car implements Prototype {
private String model;
private int year;
private Engine engine; // Nested object
public Car(String model, int year, Engine engine) {
this.model = model;
this.year = year;
this.engine = engine;
}
public void setModel(String model) { this.model = model; }
public void setYear(int year) { this.year = year; }
@Override
public Prototype clone() {
try {
// Shallow copy
Car cloned = (Car) super.clone();
// Deep copy for nested object
cloned.engine = new Engine(this.engine.getType());
return cloned;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "Car [model=" + model + ", year=" + year + ", engine=" + engine + "]";
}
}
// Nested Object
class Engine {
private String type;
public Engine(String type) { this.type = type; }
public String getType() { return type; }
@Override
public String toString() { return "Engine [type=" + type + "]"; }
}
// Usage
public class Main {
public static void main(String[] args) {
Car original = new Car("Sedan", 2023, new Engine("V6"));
System.out.println("Original: " + original);
// Clone the car
Car cloned = (Car) original.clone();
cloned.setModel("SUV"); // Modify clone
System.out.println("Cloned: " + cloned);
System.out.println("Original after cloning: " + original); // Original unchanged
}
}
/*
Original: Car [model=Sedan, year=2023, engine=Engine [type=V6]]
Cloned: Car [model=SUV, year=2023, engine=Engine [type=V6]]
Original after cloning: Car [model=Sedan, year=2023, engine=Engine [type=V6]]
*/
Code language: PHP (php)
Use case and Implementation
Creating new customer accounts by copying an existing template account, including default settings and initial balances.
//PrototypePatternDemo.java // Prototype interface or abstract class interface Prototype { Prototype clone(); } // Concrete prototype class class CustomerAccount implements Prototype { private String name; private String accountType; private double balance; // Constructor public CustomerAccount(String name, String accountType, double balance) { this.name = name; this.accountType = accountType; this.balance = balance; } // Clone method @Override public Prototype clone() { // Create a new instance by copying the current object return new CustomerAccount(this.name, this.accountType, this.balance); } // Getters and setters (optional) public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAccountType() { return accountType; } public void setAccountType(String accountType) { this.accountType = accountType; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } @Override public String toString() { return "CustomerAccount{" + "name='" + name + '\'' + ", accountType='" + accountType + '\'' + ", balance=" + balance + '}'; } } // Client class to manage prototypes class CustomerAccountManager { private CustomerAccount prototype; // Constructor to set the prototype public CustomerAccountManager(CustomerAccount prototype) { this.prototype = prototype; } // Method to create a new account by cloning the prototype public CustomerAccount createAccount(String name, String accountType, double balance) { CustomerAccount newAccount = (CustomerAccount) prototype.clone(); newAccount.setName(name); newAccount.setAccountType(accountType); newAccount.setBalance(balance); return newAccount; } } public class PrototypePatternDemo { public static void main(String[] args) { // Create a prototype account CustomerAccount prototypeAccount = new CustomerAccount("Raja", "Savings", 1000.0); // Create a manager with the prototype CustomerAccountManager manager = new CustomerAccountManager(prototypeAccount); // Create new accounts based on the prototype CustomerAccount account1 = manager.createAccount("Chakrapani", "Checking", 500.0); CustomerAccount account2 = manager.createAccount("Mahesh", "Savings", 1500.0); // Output accounts System.out.println("Prototype Account: " + prototypeAccount); System.out.println("Created Account 1: " + account1); System.out.println("Created Account 2: " + account2); } } /* C:\>javac PrototypePatternDemo.java C:\>java PrototypePatternDemo Prototype Account: CustomerAccount{name='Raja', accountType='Savings', balance=1000.0} Created Account 1: CustomerAccount{name='Chakrapani', accountType='Checking', balance=500.0} Created Account 2: CustomerAccount{name='Mahesh', accountType='Savings', balance=1500.0} */
Pros
- Performance: Faster than creating objects from scratch, especially for complex objects.
- Flexibility: Allows creating new objects with slight modifications.
- Reduced Subclassing: Avoids the need for multiple subclasses for variations.
Cons
- Complexity: Implementing deep copying can be tricky, especially with circular references.
- Cloneable Issues: Java’s Cloneable interface has limitations (e.g., no public clone() method, requires exception handling).
- Hidden Dependencies: Clients may not know what needs to be copied (shallow vs. deep).
When to Use
- When object creation is expensive (e.g., database queries, complex initialization).
- When you need to create multiple objects with minor differences.
- When you want to preserve a template object and create variations.
Real-World Example
- Copying a document template in a word processor (e.g., creating a new document based on a template).
- Duplicating graphical objects in a drawing application (e.g., shapes in a canvas).