Card 03/ 08
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.
class Memento:
__slots__ = ("_state",)
def __init__(self, state: str) -> None:
self._state = stateOriginator
Makes a memento of itself, and reads one back.
def save(self) -> Memento:
return Memento(self._state)
def restore(self, memento: Memento) -> None:
self._state = memento._stateCaretaker
Keeps mementos in order, and never reads them.
def backup(self) -> None:
self._history.append(self._originator.save())The complete template
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()state is now two
state is now three
restored to two
restored to oneimport 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());
}
}
}state is now two
state is now three
restored to two
restored to oneclass 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()));state is now two
state is now three
restored to two
restored to one
what the caretaker can see in a memento: {"brand":"memento"}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.
← → move through sections and cards