Memento
The Memento pattern captures and externalizes an object’s internal state without violating Encapsulation. It allows you to save and restore the previous state of an object without revealing the details of its implementation.
TL;DR
Allows you to capture and store the current state of an object such that it can be easily restored. This happens without exposing the internal attributes of the object itself.
Problem
If you have a text editing app, you might need to have the functionality to support undo operations. This requires saving the state of all objects and, if needed, restoring the latest snapshot from history to restore the state of all objects.
While doing this, it would appear that every object must make its attributes public such that its state can be saved - thus violating proper encapsulation. Additionally, the data must be stored in some kind of object - which should not be the object being saved itself, since that violates Single Responsibility Principle (SRP).
Solution
The Memento pattern suggests that the logic can be broken into three major classes:
- Memento → the actual storage mechanism
- Originator → the object whose state is saved/restored
- Caretaker → the object that manages saved states
For example, in a drawing application we might write the following:
from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import datetime
class Canvas:
"""
The Originator: Represents a drawing canvas. User can draw on it and undo.
"""
_drawing = ""
def __init__(self, content: str = "") -> None:
self._drawing = content
print(f"Canvas: Starting with drawing: '{self._drawing}'")
def draw(self, new_stroke: str) -> None:
"""
Simulates drawing on the canvas.
"""
print(f"Canvas: Drawing '{new_stroke}'...")
self._drawing += new_stroke
def display(self) -> None:
print(f"Canvas: Current drawing: '{self._drawing}'")
def save(self) -> DrawingMemento:
return DrawingSnapshot(self._drawing)
def restore(self, memento: DrawingMemento) -> None:
self._drawing = memento.get_drawing()
print(f"Canvas: Reverted to: '{self._drawing}'")
class DrawingMemento(ABC):
"""
Memento interface: Doesn't expose full state to the caretaker.
"""
@abstractmethod
def get_name(self) -> str:
pass
@abstractmethod
def get_date(self) -> str:
pass
class DrawingSnapshot(DrawingMemento):
"""
Concrete Memento: Stores the drawing content and timestamp.
"""
def __init__(self, drawing: str) -> None:
self._drawing = drawing
self._timestamp = str(datetime.now())[:19]
def get_drawing(self) -> str:
return self._drawing
def get_name(self) -> str:
return f"{self._timestamp} / Drawing: '{self._drawing[:10]}...'"
def get_date(self) -> str:
return self._timestamp
class History:
"""
Caretaker: Manages undo history of the canvas.
"""
def __init__(self, canvas: Canvas) -> None:
self._canvas = canvas
self._history: list[DrawingMemento] = []
def backup(self) -> None:
print("History: Saving canvas state.")
self._history.append(self._canvas.save())
def undo(self) -> None:
if not self._history:
print("History: No more undos.")
return
memento = self._history.pop()
print(f"History: Undoing to: {memento.get_name()}")
self._canvas.restore(memento)
def show_history(self) -> None:
print("\nHistory: Available snapshots:")
for memento in self._history:
print(memento.get_name())
# --- Usage ---
if __name__ == "__main__":
canvas = Canvas()
history = History(canvas)
canvas.draw("Line 1. ")
history.backup()
canvas.draw("Circle. ")
history.backup()
canvas.draw("Square. ")
history.backup()
canvas.display()
history.show_history()
print("\nUndoing actions:\n")
history.undo()
canvas.display()
history.undo()
canvas.display()