Card 03/ 08

CodeDifficulty: Intermediate3 min

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.

py
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.

py
def clone(self) -> "ConcretePrototype":
    return copy.deepcopy(self)

Client

The client copies, then changes the copy.

py
duplicate = original.clone()
duplicate.tags.append("c")

The complete template

py
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))
OutputRan with python:3.12-slim (Python 3.12.13)
original:  ConcretePrototype('configured', ['a', 'b'])
duplicate: ConcretePrototype('configured', ['a', 'b', 'c'])
same object: False
same list:   False
same class:  True
`copy.deepcopy` copies the list too, so appending to the copy leaves the original alone.
java
import 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 + ")";
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
original:  ConcretePrototype(configured, [a, b])
duplicate: ConcretePrototype(configured, [a, b, c])
same object: false
same list:   false
same class:  true
`super.clone()` copies each field as it is, so the list is copied by hand on the next line.
ts
interface 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);
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
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: false
`structuredClone` copies the data deeply but returns a plain object, so `clone()` builds the class itself.

All 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.deepcopy walks every object the original contains and copies it. copy.copy would copy only the top level, so a list inside the copy would still be shared with the original.
  • Java. Object.clone(), reached through super.clone(), makes a field-by-field copy, so a list field still points at the original's list. The class must implement the Cloneable interface, or clone() throws. Many Java developers prefer a copy constructor, new ConcretePrototype(other), which avoids Cloneable and 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. So clone() calls the constructor and deep-copies the list.