Prototype Pattern

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

  1. Prototype: An interface or abstract class declaring a clone() method for copying itself.
  2. Concrete Prototype: A class implementing the clone() method to return a copy of itself.
  3. 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).
Scroll to Top