Prototype Design Pattern

Java Provides multiple ways to create an object such as Constructor New Instance MethodDeserializationNew 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()
}

follow us on