Easy Python Patterns: Capturing the Moment with the Memento Pattern

Introduction

You can use the memento pattern to (partially) expose the internal state of an object. One use case for this pattern is to serialize an object to a file or as JSON for example, another is to build an undo stack.

One bit of advice when using this pattern is only expose the state that you need to expose, to comply with the Least Privilege Principle.

It looks like this:

This pattern usually consist of three parts:

  1. The Originator, this is the object whose internals get exposed.
  2. The Client, this is the object that wants to change the state of the Originator.
  3. Before the state changes, we instantiate Memento-object with the original state, and stored. If you want to build an undo-system, these Memento objects could be pushed onto some stack.

In an empty directory, open your IDE, and a file called ‘memento.py’:

In our sample project we will built a simple multi-level undo mechanism.

Implementation in Python

We will start by defining the Memento class:

class Memento[T]:
    def __init__(self, state: T):
        self._state = state
    
    @property
    def state(self) -> T:
        return self._state

A few notes:

  • This is a generic, it will hold values of any type
  • Note the use of @property method. Since the Memento is immutable, there is no setter.

The Originator class

The Originator class is the class whose state we want to capture:

class Originator[T]:
    def __init__(self, initial_state: T):
        self._state: T = initial_state
    
    @property
    def state(self) -> T:
        return self._state
    
    @state.setter
    def state(self, value: T):
        self._state = value
    
    def save(self) -> Memento[T]:
        return Memento(self._state)
    
    def restore(self, memento: Memento[T]):
        self._state = memento.state

A method by method explanation:

The constructor

In the constructor we initialize the originator with a starting state state

The state property

Like in the Memento class we use the @property syntax to access and modify our state.

The save() method

The save() method creates a snapshot of the current. It returns a new Memento object containing the state. This method is called when you want to save a checkpoint.

The restore() method

This restores the state from a memento. It extracts the state from the memento and sets it to the current state. We call this method during undo operations.

The Caretaker class

In the Caretaker class manages memento history without accessing internal state.

class Caretaker[T]:
    def __init__(self, originator: Originator[T]):
        self._originator = originator
        self._history: list[Memento[T]] = []
    
    def backup(self):
        self._history.append(self._originator.save())
    
    def undo(self):
        if self._history:
            self._originator.restore(self._history.pop())

Method by method:

The constructor

In the constructor, we initialize the _originator field with the supplied originator. It also initializes an empty history list. This class acts a kind of “checkpoint manager” for the originator.

The backup() method

This method saves the current state by asking the originator to create a memento. It then appends the memento to the history stack. The internal state of the memento is irrelevant to this method

The undo() method

This method starts by checking if there is any history available, if there is it pops the last memento from the and returns it. If the history is empty it does not do anything and basically returns None.

Testing it

Time to test our code:

if __name__ == "__main__":
    originator = Originator("on")
    
    caretaker = Caretaker(originator)

    caretaker.backup()
    originator.state = "off"
    caretaker.backup()
    originator.state = "standby"
    
    print(originator.state)  # standby
    caretaker.undo()
    print(originator.state)  # off
    caretaker.undo()
    print(originator.state)  # on

A short description:

  1. We start by creating an originator with an initial state.
  2. Next we create a caretaker to manage the caretaker’s history. Its initial history is empty.
  3. We fill the history by calling backup()
  4. Next we change the state twice, each time calling backup.
  5. Next we print the current state.
  6. Now we call undo() twice and report each state. This method restores from the last saved state.

Conclusion

The Memento pattern offers a powerful and elegant solution for capturing and externalizing an object’s internal state without violating encapsulation. Through the clear separation of concerns, the pattern allows us to implement complex features like multi-level undo/redo functionality, as demonstrated in our Python example, or serialization.

We successfully defined three key roles:

  • The Originator (Originator[T]), which holds the actual state and is responsible for creating a snapshot (a Memento) and restoring its state from one.
  • The Memento (Memento[T]), a simple, immutable object that holds the state captured by the Originator.
  • The Caretaker (Caretaker[T]), which manages the collection of Mementos (our “undo stack”) and orchestrates the saving and restoring process without ever inspecting the Memento’s contents.

By adhering to the Principle of Least Privilege—only exposing the necessary state within the Memento—you ensure your design is robust and maintainable. The Memento pattern is an invaluable tool in your design pattern arsenal, offering a clean, decoupled way to manage state history in your applications.

The Code Nomad
The Code Nomad
Articles: 167

Leave a Reply

Your email address will not be published. Required fields are marked *