Table of Contents
Explanation
The Prototype design pattern is a creational patter which at the core clones an object in a specific state. Basically, you set the attributes (variables) of a class and create a new instance with the exact same values of those attributes. The object creation process goes via a method which usually supports (and should) the deep cloning of objects.
Use cases/possible benefits:
- You need more objects of which are in a specific state. E.g.: Imagine a computer game where you upgrade a unit. Each new unit you build should have the upgrade, thus you can clone the latest update of the unit, which is the new prototype.
- It might reduce the need to write more (sub)classes.
- It might reduce the (computational) cost to create a new object.
Pitfalls
- The implementation of (deep) cloning can be complex.
- The runtime complexity might be higher compared to less compile time class definitions.
- The clone method returns an abstraction of the cloned object which might omit the enforcement of compile-time type checking.
- The cost of object creation might not self-evidently be less costly. For instance there might be edge cases where deep cloning is not necessary.
- If the prototype state changes, so will the next instances of the clones of that object.
Java example
For this Java example I used serialization from a dependency to clone objects. Maven dependency:
<dependency> <groupId>org.netbeans.external</groupId> <artifactId>org-apache-commons-lang3</artifactId> <version>RELEASE130</version> </dependency>
package com.chosengambit.designpatterns;
import java.io.Serializable;
import org.apache.commons.lang3.SerializationUtils;
public interface IClone extends Serializable {
default Serializable getSerializedClone() {
return SerializationUtils.clone(this);
}
}
Javapackage com.chosengambit.designpatterns;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Prototype implements IClone {
private int id;
private String test = "test";
protected List<String> codes = Arrays.asList("37432", "74265", "76742");
public boolean b = false;
public Prototype() { }
public Prototype(int id, String test, List<String> codes, boolean b) {
this.id = id;
this.test = test;
this.codes = codes;
this.b = b;
}
public void setList(List<String> codes) {
this.codes = codes;
}
public void printFields() {
try {
Field[] fieldArray = this.getClass().getDeclaredFields();
for (int i = 0; i < fieldArray.length; i++) {
Field f = fieldArray[i];
f.setAccessible(true);
System.out.println("Copying variable: "+
f.getName()+" with value "+
f.get(this)+" and type "+
f.getType().getSimpleName());
}
}
catch(Exception e) {
// beautiful error handling here
}
}
}
Javapackage com.chosengambit.designpatterns;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Prototype original = new Prototype(1, "instance", Arrays.asList("a","b","c"), true);
Prototype clone = (Prototype) original.getSerializedClone();
// print object fields, they should contain the same data
original.printFields();
clone.printFields();
// altered data of cloned object
clone.setList(Arrays.asList("123", "456", "789"));
clone.b = false;
// print object fields
original.printFields();
clone.printFields();
}
}
JavaOutput: Copying variable: id with value 1 and type int Copying variable: test with value instance and type String Copying variable: codes with value [a, b, c] and type List Copying variable: b with value true and type boolean Copying variable: id with value 1 and type int Copying variable: test with value instance and type String Copying variable: codes with value [a, b, c] and type List Copying variable: b with value true and type boolean Copying variable: id with value 1 and type int Copying variable: test with value instance and type String Copying variable: codes with value [a, b, c] and type List Copying variable: b with value true and type boolean # clone with changes Copying variable: id with value 1 and type int Copying variable: test with value instance and type String Copying variable: codes with value [123, 456, 789] and type List Copying variable: b with value false and type boolean
Python example
import copy
class Prototype:
id: int
name: str
properties: []
def clone(self):
return copy.deepcopy(self)
Python# main.py
from Prototype import Prototype
try:
p = Prototype()
p.id = 1
p.name = "House"
p.properties = ["Stone", "Red", "Flat roof"]
print(vars(p))
clone = p.clone()
print(vars(clone))
p.name = "Castle"
clone.id = 2
clone.properties.append("Large")
print(vars(p))
print(vars(clone))
except Exception as e:
print(e)
PythonOutput: {'id': 1, 'name': 'House', 'properties': ['Stone', 'Red', 'Flat roof']} {'id': 1, 'name': 'House', 'properties': ['Stone', 'Red', 'Flat roof']} {'id': 1, 'name': 'Castle', 'properties': ['Stone', 'Red', 'Flat roof']} # clone with changes {'id': 2, 'name': 'House', 'properties': ['Stone', 'Red', 'Flat roof, 'Large']}
Conclusion
The Prototype design pattern might come in handy in several use cases. As with all design patterns, overusing it can impact your code in a bad way. The examples use a more general way of implementation. It is of course also possible to implement the cloning functionality in other ways.
An alternative to this pattern is the Factory Method design pattern.
References
Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.
Wikipedia contributors. (2024, 10 september). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern