Prototype Design Pattern

0

The Prototype Pattern is used to make new copies of an existing instance. We make new copies by either:

  1. Cloning
  2. Calling Constructor thru reflection
  3. Serialization and de-serialization

Copying an object is helpful in the following cases:

  1. Creating a new object is performance intensive
  2. When one wants to change some portion of the colobe object
  3. Thread safe Object. For example a statistics kind of object that needs to be analyzed independent of the source. One can create a snapshot of the statistics which would naturally be thread safe.

Generate new objects using cloneable

Product:

package com.javarticles.patterns;

public abstract class Product implements Cloneable {
    private String productId;
    private String productName;
    private double price;

    public Product(String productId, String productName) {
        this.productId = productId;
        this.productName = productName;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getProductId() {
        return productId;
    }

    public String getProductName() {
        return productName;
    }

    public String toString() {
        return productId + ", " + productName;

    }
}

Laptop:

package com.javarticles.patterns;

public class Laptop extends Product {
    private int memory = 4;
    private int diskSizeTB = 1;

    public Laptop(String productId, String productName) {
        super(productId, productName);
    }

    public Laptop clone() {
        Laptop laptop = new Laptop(getProductId(), getProductName());
        laptop.diskSizeTB = diskSizeTB;
        laptop.memory = memory;
        return laptop;
    }

    public void addMemory(int additionalMemory) {
        memory = memory + additionalMemory;
    }

    public void addDiskSize(int additionalDiskSizeTB) {
        diskSizeTB = diskSizeTB + additionalDiskSizeTB;
    }

    public String toString() {
        return super.toString() + "M(" + memory
                + "), disk(" + diskSizeTB + ")";

    }
}

LaptopCustomizer:

package com.javarticles.patterns;

public class LaptopCustomizer {
    public Laptop customize(Laptop basicModel, int additionalMemory, int additionalDiskSize) {
        Laptop customizedModel = basicModel.clone();
        customizedModel.addMemory(4);
        customizedModel.addDiskSize(2);
        return customizedModel;
    }
    
    public static void main(String[] args) {
        Laptop baseLaptop = new Laptop("01", "LaptopCustomized");
        LaptopCustomizer customizer = new LaptopCustomizer();
        Laptop customizedLaptop = customizer.customize(baseLaptop, 2, 1);
        System.out.println(customizedLaptop);
    }

}

Output:

01, LaptopCustomizedM(8), disk(3)

Prototype Factory

In this example we will see how to write a generic client code which is not directly coupled with any specific type but would still be able to make new instances without knowing which specific class is being instantiated.
IFactory<T> is a factory implementation that creates a new instance each time create() is called.
PrototypeFactory.prototypeFactory(prototype) creates a factory based on a prototype object. It will return a clone of the same prototype object each time the factory is used.
The prototype will be cloned using clone() method.
Suppose clone() method is missing, one may try copying by either invoking the prototype’s constructor using reflection or thru serialization and deserialization of the prototype object.

IFactory:

package com.javarticles.patterns;

public interface IFactory<T> {
    T create();
}

PrototypeFactory:

package com.javarticles.patterns;

import java.lang.reflect.Method;

public class PrototypeFactory {
    public static <T> IFactory<T> prototypeFactory(final T prototype) {
        try {
            final Method method = prototype.getClass().getMethod("clone", (Class[]) null);
            return new InnerPrototypeFactory<T>(prototype, method);

        } catch (final NoSuchMethodException ex) {
            throw new IllegalArgumentException("The prototype must implement cloneable");
        }
        
    }

    private PrototypeFactory() {
        super();
    }

    static class InnerPrototypeFactory<T> implements IFactory<T> {

        private final T prototype;
        private transient Method cloneMethod;


        private InnerPrototypeFactory(final T prototype, final Method method) {
            this.prototype = prototype;
            this.cloneMethod = method;
        }

        public T create() {
            try {
                return (T) cloneMethod.invoke(prototype, (Object[]) null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    public static void main(String[] args) {
        Product laptop = new Laptop("01", "Highend Laptop");
        Product book = new Book("02", "Design pattern", "");
        IFactory<Product> laptopFactory = PrototypeFactory.prototypeFactory(laptop);
        IFactory<Product> bookFactory = PrototypeFactory.prototypeFactory(book);
        Product newLaptop = laptopFactory.create();
        Product newBook = bookFactory.create();
        System.out.println("Laptop created using prototype factory: " + newLaptop);
        System.out.println("Book created using prototype factory: " + newBook);
    }
}

Output:

Laptop created using prototype factory: 01, Highend LaptopM(4), disk(1)
Book created using prototype factory: 02, Design pattern, 

Creating a thread safe snapshot object using prototype

In the below example, we use prototype pattern to create a snapshot of statistics.
Imagine an application where we build method call statistics to keep track of which method is taking more execution time. When one decides to analyze the statistics, we create a snapshot of the statistics so that the statistics is thread safe unaffected by the source statistics object. The client here is the StatisticsManager which is kind of independent of various specific statistics objects that are created and maintained. For example, method statistics, summary of statistics, one can add new statistics like SQL statistics to the existing set of data structures.

IStatistics represents the common interface that a specific statistics class will implement. Instead of clone() we use copy() method.
IStatistics:

package com.javarticles.patterns;

public interface IStatistics {
    IStatistics copy();
    String getDetails();
    long executionTime();
}

Method call statistics knows the method name and its execution time.
CallStatistics:

package com.javarticles.patterns;

public class CallStatistics implements IStatistics {
    private String method;
    private long executionTime;
    
    public CallStatistics(String method, long executionTime) {
        this.method = method;
        this.executionTime = executionTime;
    }
   
    public IStatistics copy() {
        return new CallStatistics(method, executionTime);
    }

    public String getDetails() {
        return method;
    }

    public long executionTime() {
        return executionTime;
    }
    
    public String toString() {
        return method + "(" + executionTime + ")";
    }
}

SummaryStatistics represents the summary of the statistics.
SummaryStatistics:

package com.javarticles.patterns;

public class SummaryStatistics implements IStatistics {
   private long totalExecutionTime;
   
    public SummaryStatistics(long executionTime) {
        this.totalExecutionTime = executionTime;
    }
    
    public IStatistics copy() {
        return new SummaryStatistics(totalExecutionTime);
    }

    public String getDetails() {
        return "Summary statistics";
    }

    public long executionTime() {
        return totalExecutionTime;
    }

    public void addToStatistics(long executionTime) {
        this.totalExecutionTime = this.totalExecutionTime + executionTime;
    }
    
    public String toString() {
        return getDetails() + "(" + totalExecutionTime + ")";
    }
}

StatisticsManager is the client that has separate methods to call to create summary as well as detailed statistics.
StatisticsManager:

package com.javarticles.patterns;

import java.util.ArrayList;
import java.util.List;

public class StatisticsManager {
    private SummaryStatistics summaryStatistics;
    private List<IStatistics> detailedStatistics = new ArrayList<IStatistics>();
    
    public IStatistics createSummarySnapshot() {
        return summaryStatistics.copy();
    }
    
    public List<IStatistics> createDetailedSnapshot() {
        List<IStatistics> details = new ArrayList<IStatistics>(detailedStatistics.size());
        for (IStatistics detail : detailedStatistics) {
            details.add(detail.copy());
        }
        return details;
    }
    
    public void addToStatistics(String method, long executionTime) {
        detailedStatistics.add(new CallStatistics(method, executionTime));
        if (summaryStatistics == null) {
            summaryStatistics = new SummaryStatistics(executionTime);
        } else {
            summaryStatistics.addToStatistics(executionTime);
        }
    }
    
    public String toString() {
        List<IStatistics> detailSnapshot = createDetailedSnapshot();
        IStatistics summarySnapshot = createSummarySnapshot();
        StringBuilder sb = new StringBuilder();
        sb.append(summarySnapshot)
          .append("\n")
          .append("Details:\n")
          .append(detailSnapshot);
        return sb.toString();
    }
    
    public static void main(String[] args) {
        StatisticsManager sm = new StatisticsManager();
        sm.addToStatistics("M1", 5);
        sm.addToStatistics("M2", 15);
        sm.addToStatistics("M3", 2);
        
        System.out.println("Snapshot:\n" + sm);        
    }
}

Output:

Snapshot:
Summary statistics(22)
Details:
[M1(5), M2(15), M3(2)]

Download the source code

This was an example about prototype design pattern.

You can download the source code here: javaPrototypeDesignPattern.zip

About Author

Ram's expertise lies in test driven development and re-factoring. He is passionate about open source technologies and loves blogging on various java and open-source technologies like spring. You can reach him at [email protected]

Leave A Reply