What is a Prototype Design Pattern
In software development, the Prototype design pattern provides a mechanism for creating new objects by cloning existing ones. It allows the creation of object instances without depending on complex initialization logic, resulting in improved performance and flexibility.
The Prototype pattern is a creational design pattern in Java that allows you to create copies (clones) of objects without making the code dependent on their concrete classes. It provides a way to copy existing objects instead of creating new instances from scratch. This can be useful when object creation is expensive or complex, and you want to create variations of objects with different configurations.
Prototype pattern is used when the Object creation is a costly affair and requires a lot of time and resources and you have a similar object already existing. So this pattern provides a mechanism to copy the original object to a new object and then modify it according to our needs. This pattern uses java cloning to copy the object.
Explanation
The pattern defines a prototype interface or abstract class that declares a method for cloning objects, and concrete classes implement this interface to provide their cloning logic.
It would be easy to understand this pattern with an example, suppose we have an Object that loads data from database. Now we need to modify this data in our program multiple times, so it’s not a good idea to create the Object using new keyword and load all the data again from database. So the better approach is to clone the existing object into a new object and then do the data manipulation.
** Prototype design pattern mandates that the Object which you are copying should provide the copying feature. It should not be done by any other class. However whether to use shallow or deep copy of the Object properties depends on the requirements and it’s a design decision.
- One example of how this can be useful is if an original object is created with a resource such as a data stream that may not be available at the time that a clone of the object is needed. Another example is if the original object creation involves a significant time commitment, such as reading data from a database or over a network.
Key Features of the Prototype Design Pattern:
- Prototype Interface/Abstract Class: The Prototype design pattern defines a prototype interface or abstract class with a clone method like doClone() that enables the creation of new objects by cloning existing ones. The doClone() method returns the object of same type.
- Concrete Prototypes: Concrete prototype classes implement the prototype interface/abstract class and provide their cloning logic. They create new instances by copying the data from an existing object.
- Client: The client class is responsible for requesting new object instances from the prototypes by invoking the clone method like doClone().
Benefits
- Reduces object creation overhead: The Prototype pattern avoids costly object initialization processes by creating new objects through cloning.
- Improves performance: Creating new objects by cloning is typically faster than invoking constructors and initializing object properties.
- Enhances flexibility: Prototypes allow dynamic creation of objects at runtime based on existing instances, providing flexibility in object creation.
- Supports object customization: Cloned objects can be modified individually without affecting the original prototype
Usage in JDK : java.lang.Object#clone ()
Implementing the Prototype Design Pattern in Java
Let’s demonstrate the implementation of the Prototype design pattern using Java code. We’ll create an example of a Shape prototype that allows the creation of different shapes by cloning existing ones.
// Prototype Interface - Shape
public interface Shape extends Cloneable {
void draw();
Shape doClone();
}
// Concrete Prototype - Rectangle
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle.");
}
@Override
public Shape doClone() {
try {
return (Shape) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
// Concrete Prototype - Circle
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
@Override
public Shape doClone() {
try {
return (Shape) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
public class Application {
public static void main(String[] args) {
Shape rectangle = new Rectangle();
Shape clonedRectangle = rectangle.doClone();
rectangle.draw(); // Output: Drawing a rectangle.
clonedRectangle.draw(); // Output: Drawing a rectangle.
Shape circle = new Circle();
Shape clonedCircle = circle.doClone();
circle.draw(); // Output: Drawing a circle.
clonedCircle.draw(); // Output: Drawing a circle.
}
}
The Shape interface serves as the prototype interface, declaring the draw method for drawing shapes and the clone method for creating new instances.
The Rectangle and Circle classes are concrete prototype implementations that implement the Shape interface. They define their own cloning logic by overriding the clone method and provide the implementation for the draw method.
In the above example, we create instances of the Rectangle and Circle shapes. We then clone these objects using their doClone method, resulting in new instances that are independent but identical to the originals. Finally, we call the draw method on both the original and cloned objects, verifying their functionality.
Use cases
- When the classes to instantiate are specified at run-time, for example, by dynamic
loading; or - To avoid building a class hierarchy of factories that parallels the class hierarchy of
products; or - When instances of a class can have one of only a few different combinations of state. It
may be more convenient to install a corresponding number of prototypes and clone them rather than instantiating the class manually, each time with the appropriate state.
Implementation of Prototype pattern – real example
package com.rndayala.designpatterns.prototype;
//Product interface
public interface Product extends Cloneable {
void setName(String name);
String getName();
void setPrice(double price);
double getPrice();
Product clone();
}
//Concrete product class: Shirt
public class Shirt implements Product {
private String name;
private double price;
public Shirt(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public double getPrice() {
return price;
}
@Override
public Product clone() {
return new Shirt(this.name, this.price);
}
}
//Concrete product class: Pants
public class Pants implements Product {
private String name;
private double price;
public Pants(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public double getPrice() {
return price;
}
@Override
public Product clone() {
return new Pants(this.name, this.price);
}
}
//Concrete product class: Shoes
public class Shoes implements Product {
private String name;
private double price;
public Shoes(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public double getPrice() {
return price;
}
@Override
public Product clone() {
return new Shoes(this.name, this.price);
}
}
// Client code
public class Demo {
public static void main(String[] args) {
Product shirt1 = new Shirt("Basic Shirt", 25.0);
Product pant1 = new Pants("Casual Pants", 40.0);
Product shoe1 = new Shoes("Sports Shoes", 60.0);
Product shirt2 = shirt1.clone();
shirt2.setName("Fancy Shirt");
Product pant2 = pant1.clone();
pant2.setPrice(50.0);
System.out.println("Shirt: " + shirt2.getName() + ", Price: $" + shirt2.getPrice());
System.out.println("Pants: " + pant2.getName() + ", Price: $" + pant2.getPrice());
System.out.println("Shoes: " + shoe1.getName() + ", Price: $" + shoe1.getPrice());
}
}
In this example, we have implemented the Prototype pattern in the context of an e-commerce application. The Product interface acts as the prototype, and the concrete product classes (Shirt, Pants, and Shoes) implement the cloning functionality. This allows for easy creation of variations of products with different configurations without the need to create new objects from scratch.
The Prototype design pattern offers an efficient way to create new objects by cloning existing ones, eliminating the need for complex initialization processes. By utilizing the prototype interface and concrete prototype classes, the pattern enables the creation of new instances while preserving their characteristics. In Java, the Prototype pattern is commonly used when object creation is expensive, or when objects need to be customized based on existing instances.