Card 03/ 08
Prototype Template in Python, Java and TypeScript
clone() in Java, copy.deepcopy in Python, structuredClone in TypeScript.
The Prototype template is the pattern in its pure form: a prototype interface with clone(), and one concrete prototype holding a name and a list of tags. Each language copies the object a different way, and the template checks the same three things in each: the copy is a new object, it does not share the list, and it has the original's class.
Building the template one participant at a time
Prototype
The interface declares one method.
class Prototype(ABC):
@abstractmethod
def clone(self) -> "Prototype": ...Concrete prototype
The object copies itself. In Python, copy.deepcopy copies the object and everything it contains.
def clone(self) -> "ConcretePrototype":
return copy.deepcopy(self)Client
The client copies, then changes the copy.
duplicate = original.clone()
duplicate.tags.append("c")The complete template
import copy
from abc import ABC, abstractmethod
class Prototype(ABC):
@abstractmethod
def clone(self) -> "Prototype": ...
class ConcretePrototype(Prototype):
def __init__(self, name: str, tags: list[str]) -> None:
self.name = name
self.tags = tags
def clone(self) -> "ConcretePrototype":
return copy.deepcopy(self)
def __repr__(self) -> str:
return f"ConcretePrototype({self.name!r}, {self.tags})"
original = ConcretePrototype("configured", ["a", "b"])
duplicate = original.clone()
duplicate.tags.append("c")
print("original: ", original)
print("duplicate:", duplicate)
print("same object:", duplicate is original)
print("same list: ", duplicate.tags is original.tags)
print("same class: ", type(duplicate) is type(original))original: ConcretePrototype('configured', ['a', 'b'])
duplicate: ConcretePrototype('configured', ['a', 'b', 'c'])
same object: False
same list: False
same class: Trueimport java.util.ArrayList;
import java.util.List;
public class PtTemplate {
public static void main(String[] args) {
ConcretePrototype original = new ConcretePrototype("configured", new ArrayList<>(List.of("a", "b")));
ConcretePrototype duplicate = original.clone();
duplicate.tags.add("c");
System.out.println("original: " + original);
System.out.println("duplicate: " + duplicate);
System.out.println("same object: " + (duplicate == original));
System.out.println("same list: " + (duplicate.tags == original.tags));
System.out.println("same class: " + (duplicate.getClass() == original.getClass()));
}
}
interface Prototype extends Cloneable {
Prototype clone();
}
class ConcretePrototype implements Prototype {
String name;
List<String> tags;
ConcretePrototype(String name, List<String> tags) {
this.name = name;
this.tags = tags;
}
@Override
public ConcretePrototype clone() {
try {
ConcretePrototype copy = (ConcretePrototype) super.clone();
copy.tags = new ArrayList<>(tags);
return copy;
} catch (CloneNotSupportedException impossible) {
throw new AssertionError(impossible);
}
}
@Override
public String toString() {
return "ConcretePrototype(" + name + ", " + tags + ")";
}
}original: ConcretePrototype(configured, [a, b])
duplicate: ConcretePrototype(configured, [a, b, c])
same object: false
same list: false
same class: trueinterface Prototype {
clone(): Prototype;
}
class ConcretePrototype implements Prototype {
constructor(public name: string, public tags: string[]) {}
clone(): ConcretePrototype {
return new ConcretePrototype(this.name, structuredClone(this.tags));
}
}
const original = new ConcretePrototype("configured", ["a", "b"]);
const duplicate = original.clone();
duplicate.tags.push("c");
console.log("original: ", original);
console.log("duplicate:", duplicate);
console.log("same object:", duplicate === original);
console.log("same list: ", duplicate.tags === original.tags);
console.log("same class: ", duplicate instanceof ConcretePrototype);
const plain = structuredClone(original);
console.log("structuredClone keeps the class:", plain instanceof ConcretePrototype);original: ConcretePrototype { name: 'configured', tags: [ 'a', 'b' ] }
duplicate: ConcretePrototype { name: 'configured', tags: [ 'a', 'b', 'c' ] }
same object: false
same list: false
same class: true
structuredClone keeps the class: falseAll three print the same result: the copy has a third tag, the original still has two, and the two objects share neither identity nor list. Each language gets there a different way.
- Python.
copy.deepcopywalks every object the original contains and copies it.copy.copywould copy only the top level, so a list inside the copy would still be shared with the original. - Java.
Object.clone(), reached throughsuper.clone(), makes a field-by-field copy, so a list field still points at the original's list. The class must implement theCloneableinterface, orclone()throws. Many Java developers prefer a copy constructor,new ConcretePrototype(other), which avoidsCloneableand the checked exception entirely. - TypeScript.
structuredClone, built into Node 17 and later and every current browser, copies data deeply, but its result is a plain object: the last line shows that cloning the instance itself loses the class. Soclone()calls the constructor and deep-copies the list.
← → move through sections and cards