CodeDifficulty: Intermediate3 min

Memento Template in Python, Java and TypeScript

A Java nested class, a TypeScript private field, and Python's convention of a leading underscore.

The Memento template is the pattern in its pure form, with the participant names from the class diagram. An originator's state goes from "one" to "two" to "three", with a backup before each change, and the caretaker then undoes three times. The steps build the Python version, and the tabs give the complete program in three languages, each with its output and its own way of keeping the memento closed.

Building the template one part at a time

Memento

Holds the state, marked private by a leading underscore.

py
class Memento:
    __slots__ = ("_state",)

    def __init__(self, state: str) -> None:
        self._state = state

Originator

Makes a memento of itself, and reads one back.

py
def save(self) -> Memento:
    return Memento(self._state)

def restore(self, memento: Memento) -> None:
    self._state = memento._state

Caretaker

Keeps mementos in order, and never reads them.

py
def backup(self) -> None:
    self._history.append(self._originator.save())

The complete template

py
class Memento:
    __slots__ = ("_state",)

    def __init__(self, state: str) -> None:
        self._state = state


class Originator:
    def __init__(self, state: str) -> None:
        self._state = state

    def change(self, state: str) -> None:
        self._state = state
        print("state is now", self._state)

    def save(self) -> Memento:
        return Memento(self._state)

    def restore(self, memento: Memento) -> None:
        self._state = memento._state
        print("restored to", self._state)


class Caretaker:
    def __init__(self, originator: Originator) -> None:
        self._originator = originator
        self._history: list[Memento] = []

    def backup(self) -> None:
        self._history.append(self._originator.save())

    def undo(self) -> None:
        if self._history:
            self._originator.restore(self._history.pop())


originator = Originator("one")
caretaker = Caretaker(originator)
caretaker.backup()
originator.change("two")
caretaker.backup()
originator.change("three")
caretaker.undo()
caretaker.undo()
caretaker.undo()
OutputRan with python:3.12-slim (Python 3.12.13)
state is now two
state is now three
restored to two
restored to one
`_state` is private by convention only; Python does not stop the caretaker reading it.
java
import java.util.ArrayDeque;
import java.util.Deque;

public class MmTemplate {
    public static void main(String[] args) {
        Originator originator = new Originator("one");
        Caretaker caretaker = new Caretaker(originator);
        caretaker.backup();
        originator.change("two");
        caretaker.backup();
        originator.change("three");
        caretaker.undo();
        caretaker.undo();
        caretaker.undo();
    }
}

class Originator {
    private String state;

    Originator(String state) {
        this.state = state;
    }

    void change(String state) {
        this.state = state;
        System.out.println("state is now " + state);
    }

    Memento save() {
        return new Memento(state);
    }

    void restore(Memento memento) {
        state = memento.state;
        System.out.println("restored to " + state);
    }

    static final class Memento {
        private final String state;

        private Memento(String state) {
            this.state = state;
        }
    }
}

class Caretaker {
    private final Originator originator;
    private final Deque<Originator.Memento> history = new ArrayDeque<>();

    Caretaker(Originator originator) {
        this.originator = originator;
    }

    void backup() {
        history.push(originator.save());
    }

    void undo() {
        if (!history.isEmpty()) {
            originator.restore(history.pop());
        }
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
state is now two
state is now three
restored to two
restored to one
The memento's field and constructor are private, and only `Originator`, its enclosing class, can reach them.
ts
class Memento {
  private readonly brand = "memento";
}

class Originator {
  #snapshots = new WeakMap<Memento, string>();

  constructor(private state: string) {}

  change(state: string): void {
    this.state = state;
    console.log("state is now", this.state);
  }

  save(): Memento {
    const memento = new Memento();
    this.#snapshots.set(memento, this.state);
    return memento;
  }

  restore(memento: Memento): void {
    this.state = this.#snapshots.get(memento) ?? this.state;
    console.log("restored to", this.state);
  }
}

class Caretaker {
  private readonly history: Memento[] = [];

  constructor(private readonly originator: Originator) {}

  backup(): void {
    this.history.push(this.originator.save());
  }

  undo(): void {
    const memento = this.history.pop();
    if (memento) {
      this.originator.restore(memento);
    }
  }
}

const originator = new Originator("one");
const caretaker = new Caretaker(originator);
caretaker.backup();
originator.change("two");
caretaker.backup();
originator.change("three");
caretaker.undo();
caretaker.undo();
caretaker.undo();
console.log("what the caretaker can see in a memento:", JSON.stringify(originator.save()));
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
state is now two
state is now three
restored to two
restored to one
what the caretaker can see in a memento: {"brand":"memento"}
The state is in the originator's `#snapshots`, so a memento carries nothing the caretaker can read.

All three print the same: two changes, then restores to "two" and "one". The third undo finds the history empty and does nothing.

The languages differ in how firmly the memento is sealed. Java nests Memento inside Originator with a private field, and Java lets an enclosing class read its nested class's private members, so the caretaker can hold Originator.Memento and cannot read it. TypeScript's #snapshots is a private field enforced by the JavaScript runtime: the memento is an empty token, and the last line shows that serialising one reveals only its marker. Python has no enforced privacy, so _state is a convention: the caretaker could read it, and the pattern relies on it not doing so.