Java Provides multiple ways to create an object
such as Constructor New Instance Method, Deserialization, New Instance Method, and New Keyword.
Sometimes creating an object
takes a lot of complex operations, processing, and resource. A Prototype Design Pattern
is one of the Creational Design Patterns
that allows the creation of new objects by either cloning or copying existing objects. It takes an existing object
and created a copy of it, and modifies the values as per requirement. It hides the internal complexity of creating the new object
and improves the overall performance.
A prototype Design Pattern provides a way to create a new Object with the help of Existing.
let's understand the requirement of Prototype Design Pattern
with some real-world examples
Suppose an Object
requires certain values from an external API
in order to be created, so calling an external API
every time before creating the Object
will take a lot of time. so a better approach is to create an Object
with the flow and then create other objects by copying the values.
The Prototype Design Pattern
creates a copy of the Object
. it can be done via Copy Constructor
or by Overriding
the built-in clone
method of Object class
and implementing the Clonable Interface
. A class can have primitive
variables and references
to other classes as well. clone
method takes an original object
and creates a new Object
in 2 ways. shallow copy
and deep copy
, let's understand both
Shallow Copy
A class can have a reference
to another object
, the clone
method while copying the object
if only copies the primitive
values of the object but the reference
to other objects remains unchanged. the changes made to reference
values will be reflected in the original. hence called a Shallow Copy
. it is preferred when classes have primitive data types
and immutable references
only.
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Apple implements Cloneable {
private String name;
private String taste;
private Vendor vendor;
Apple(Apple apple) {
this.name = apple.getName();
this.taste = apple.getTaste();
this.vendor = apple.getVendor();
}
public Apple clone() throws CloneNotSupportedException {
return (Apple) super.clone();
}
}
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Vendor implements Cloneable {
private int vendorId;
private String vendorName;
}
package org.wesome.design.patterns;
public class Prototype {
public static void main(String[] args) throws CloneNotSupportedException {
Apple apple = new Apple("Apple", "Sweet", new Vendor(1, "VendorA"));
Apple macintosh = apple.clone();
/* changing value of parent object */
macintosh.setName("Macintosh");
/* changing value of reference object */
macintosh.getVendor().setVendorName("vendorB");
System.out.println("Original Apple Object " + apple);
System.out.println("Clone Object = " + macintosh);
}
}
package org.wesome.design.patterns;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class PrototypeTest {
@Test
void testCloneMethod() throws CloneNotSupportedException {
Apple apple = new Apple("Apple", "Sweet", new Vendor(1, "VendorA"));
Apple macintosh = apple.clone();
/* changing value of parent object */
macintosh.setName("Macintosh");
/* changing value of reference object */
macintosh.getVendor().setVendorName("VendorB");
Assertions.assertAll(() -> {
Assertions.assertTrue(macintosh instanceof Apple);
Assertions.assertNotEquals(apple, macintosh);
Assertions.assertEquals(apple.getName(), "Apple");
Assertions.assertEquals(apple.getTaste(), "Sweet");
Assertions.assertEquals(macintosh.getName(), "Macintosh");
Assertions.assertEquals(macintosh.getTaste(), "Sweet");
Assertions.assertEquals(apple.getVendor().getVendorId(), 1);
Assertions.assertEquals(apple.getVendor().getVendorName(), "VendorB");
Assertions.assertEquals(macintosh.getVendor().getVendorId(), 1);
Assertions.assertEquals(macintosh.getVendor().getVendorName(), "VendorB");
Assertions.assertNotEquals(apple.getName(), macintosh.getName());
Assertions.assertEquals(apple.getTaste(), macintosh.getTaste());
});
}
@Test
void testCopyConstructor() {
Apple apple = new Apple("Apple", "Sweet", new Vendor(1, "VendorA"));
Apple macintosh = new Apple(apple);
/* changing value of parent object */
macintosh.setName("Macintosh");
/* changing value of reference object */
macintosh.getVendor().setVendorName("VendorB");
Assertions.assertAll(() -> {
Assertions.assertTrue(macintosh instanceof Apple);
Assertions.assertNotEquals(apple, macintosh);
Assertions.assertEquals(apple.getName(), "Apple");
Assertions.assertEquals(apple.getTaste(), "Sweet");
Assertions.assertEquals(macintosh.getName(), "Macintosh");
Assertions.assertEquals(macintosh.getTaste(), "Sweet");
Assertions.assertEquals(apple.getVendor().getVendorId(), 1);
Assertions.assertEquals(apple.getVendor().getVendorName(), "VendorB");
Assertions.assertEquals(macintosh.getVendor().getVendorId(), 1);
Assertions.assertEquals(macintosh.getVendor().getVendorName(), "VendorB");
Assertions.assertNotEquals(apple.getName(), macintosh.getName());
Assertions.assertEquals(apple.getTaste(), macintosh.getTaste());
});
}
}
plugins {
id 'java'
id "io.freefair.lombok" version "6.2.0"
}
group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}
test {
useJUnitPlatform()
}
Deep Copy
while coping the object
, the primitive
and the reference
both get copied into a new instance
, and changes made to other class reference variables
will not be reflected on the original object
as well. hence it's called a Deep Copy
. it's preferred when classes have mutable references
.
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Apple implements Cloneable {
private String name;
private String taste;
private Vendor vendor;
Apple(Apple apple) {
this.name = apple.getName();
this.taste = apple.getTaste();
this.vendor = new Vendor(apple.getVendor());
}
protected Apple clone() throws CloneNotSupportedException {
Apple apple = (Apple) super.clone();
apple.vendor = vendor.clone();
return apple;
}
}
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Vendor implements Cloneable {
private int vendorId;
private String vendorName;
Vendor(Vendor vendor) {
this.vendorId = vendor.getVendorId();
this.vendorName = vendor.getVendorName();
}
protected Vendor clone() throws CloneNotSupportedException {
return (Vendor) super.clone();
}
}
package org.wesome.design.patterns;
public class Prototype {
public static void main(String[] args) throws CloneNotSupportedException {
Apple apple = new Apple("Apple", "Sweet", new Vendor(1, "vendorA"));
Apple macintosh = apple.clone();
/* changing value of parent object */
macintosh.setName("Macintosh");
/* changing value of reference object */
macintosh.getVendor().setVendorName("vendorB");
System.out.println("Original Apple Object = " + apple);
System.out.println("Clone Object = " + macintosh);
}
}
package org.wesome.design.patterns;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class PrototypeTest {
@Test
void testCloneMethod() throws CloneNotSupportedException {
Apple apple = new Apple("Apple", "Sweet", new Vendor(1, "VendorA"));
Apple macintosh = apple.clone();
/* changing value of parent object */
macintosh.setName("Macintosh");
/* changing value of reference object */
macintosh.getVendor().setVendorName("VendorB");
Assertions.assertAll(() -> {
Assertions.assertTrue(macintosh instanceof Apple);
Assertions.assertNotEquals(apple, macintosh);
Assertions.assertEquals(apple.getName(), "Apple");
Assertions.assertEquals(apple.getTaste(), "Sweet");
Assertions.assertEquals(macintosh.getName(), "Macintosh");
Assertions.assertEquals(macintosh.getTaste(), "Sweet");
Assertions.assertEquals(apple.getVendor().getVendorId(), 1);
Assertions.assertEquals(apple.getVendor().getVendorName(), "VendorA");
Assertions.assertEquals(macintosh.getVendor().getVendorId(), 1);
Assertions.assertEquals(macintosh.getVendor().getVendorName(), "VendorB");
Assertions.assertNotEquals(apple.getName(), macintosh.getName());
Assertions.assertEquals(apple.getTaste(), macintosh.getTaste());
});
}
@Test
void testCopyConstructor() {
Apple apple = new Apple("Apple", "Sweet", new Vendor(1, "VendorA"));
Apple macintosh = new Apple(apple);
/* changing value of parent object */
macintosh.setName("Macintosh");
/* changing value of reference object */
macintosh.getVendor().setVendorName("VendorB");
Assertions.assertAll(() -> {
Assertions.assertTrue(macintosh instanceof Apple);
Assertions.assertNotEquals(apple, macintosh);
Assertions.assertEquals(apple.getName(), "Apple");
Assertions.assertEquals(apple.getTaste(), "Sweet");
Assertions.assertEquals(macintosh.getName(), "Macintosh");
Assertions.assertEquals(macintosh.getTaste(), "Sweet");
Assertions.assertEquals(apple.getVendor().getVendorId(), 1);
Assertions.assertEquals(apple.getVendor().getVendorName(), "VendorB");
Assertions.assertEquals(macintosh.getVendor().getVendorId(), 1);
Assertions.assertEquals(macintosh.getVendor().getVendorName(), "VendorB");
Assertions.assertNotEquals(apple.getName(), macintosh.getName());
Assertions.assertEquals(apple.getTaste(), macintosh.getTaste());
});
}
}
plugins {
id 'java'
id "io.freefair.lombok" version "6.2.0"
}
group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}
test {
useJUnitPlatform()
}