TUTORIAL SERIES

Design Patterns in Python: Memento

Snapshot of Time

Amir Lavasani
8 min readJan 10, 2024

Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. That’s precisely what design patterns provide. In this series, we’ll explore what these patterns are and how they can elevate your coding skills.

Understanding the Memento Pattern

What is the Memento Design Pattern?

The Memento Design Pattern, a behavioral design pattern, specializes in capturing and storing an object’s state for future retrieval without compromising its functionality.

This pattern enables restoring an object to a prior state without causing disruptions, effectively managing an object’s history.

When to Use the Memento Pattern:

The Memento Design Pattern proves beneficial in various software scenarios:

  1. Preserving Object History: Apply it when you need to maintain a record of an object’s state changes over time.
  2. Undo/Redo Functionality: Implement it to enable undo and redo operations, allowing users to revert to previous states effortlessly.
  3. Checkpointing or Snapshotting: Utilize it in systems requiring periodic checkpoints or snapshots of their states for recovery or analysis.
  4. Managing State Transitions: Employ it when managing complex state transitions in an application without altering the objects' integrity.

Practical Example: Game Save System

In this quick demo, we’ll see how the Memento Design Pattern helps save and reload game progress. This example shows how the pattern manages and brings back game states.

We’ll use classes like Originator for managing the game, Memento for saving states, and Caretaker for handling saves.

Dall-E generated image with the following concept: Time Capsule, symbolizing preservation and encapsulation of memories or moments

Terminology and Key Components

Understanding the core components of the Memento Design Pattern is pivotal. Here are the key elements of Memento:

  1. Originator: Manages the object’s state, providing methods for its storage and retrieval.
  2. Memento: A container capturing an object’s state, offering retrieval methods without external interference.
  3. Caretaker: Safeguards Mementos without altering their content, storing them for future use.
  4. State: The internal data encapsulated by the Originator, stored within the Memento for potential restoration.

Memento Implementation Variants

The Memento pattern offers three distinct implementation variants, each catering to different programming environments and encapsulation needs:

1. Implementation based on Nested Classes

  • Classic Approach: Relies on support for nested classes available in languages like C++, C#, and Java.
  • Functionality: Originator creates and restores state snapshots.
  • Memento Role: Serves as an immutable snapshot of the Originator’s state passed via constructor.
  • Caretaker’s Role: Manages state capture and restoration through a stack of Mementos.
  • Structure: Memento class is nested inside Originator, enabling limited access for Caretakers and full access for Originators.
Memento design pattern structure diagram. Image from refactoring.guru

2. Implementation based on an Intermediate Interface

  • Alternative Approach: Suited for languages lacking nested class support like PHP.
  • Access Control: Caretakers interact with Memento via a predefined interface, restricting access to metadata methods.
  • Direct Interaction: Originators directly engage with Memento members, necessitating public declaration of all Memento attributes.
Memento design pattern structure diagram. Image from refactoring.guru

3. Implementation with Even Stricter Encapsulation

  • Maximum Security: Ideal when absolute privacy between the Originator and Memento is paramount.
  • Distinct Originators and Mementos: Each Originator pairs with a unique Memento class, ensuring complete privacy.
  • Restricted Access: Both Originators and Mementos guard their states, restricting external access.
  • Restoration Independence: The Caretaker’s role shifts as Memento itself defines the restoration method, linking each Memento to its Originator for state restoration.
Memento design pattern structure diagram. Image from refactoring.guru

Memento Pattern Implementation in Python

Step 1: Originator

Begin by defining the Originator class, representing the object whose state needs to be preserved. This class encapsulates the object’s state and provides methods to set and retrieve its state.

class Originator:
def __init__(self, state):
self._state = state

def create_memento(self):
return Memento(self._state)

def restore(self, memento):
self._state = memento.get_state()

def set_state(self, state):
self._state = state

def get_state(self):
return self._state

Step 2: Memento

Next, create the Memento class responsible for storing the state of the Originator object. This class stores the object’s state and provides methods to retrieve the stored state.

class Memento:
def __init__(self, state):
self._state = state

def get_state(self):
return self._state

Step 3: Caretaker

Define the Caretaker class, responsible for managing and storing multiple Mementos. This class maintains a collection of Mementos, ensuring their safekeeping.

class Caretaker:
def __init__(self):
self._mementos = []

def add_memento(self, memento):
self._mementos.append(memento)

def get_memento(self, index):
return self._mementos[index]

Step 4: Client

Instantiate the Originator, Memento, and Caretaker classes to demonstrate the Memento pattern in action. Use the methods provided by these classes to capture, store, and restore object states as needed within your application.

if __name__ == "__main__":
# Instantiate classes
originator = Originator("Initial state")
caretaker = Caretaker()

# Store object state
caretaker.add_memento(originator.create_memento())

# Modify object state
originator.set_state("Modified state")

# Restore object state
originator.restore(caretaker.get_memento(0))

GitHub Repo 🎉

Explore all code examples and design pattern implementations on GitHub!

Practical Example Implementation

This code simulates a simple game where playing advances the game level. It demonstrates saving the game state, progressing further in the game, loading the saved state, and restoring the game to the previously saved level.

The GameLevel attribute showcases state changes and reverting to the previous state using the Memento pattern.

Step 1: Originator Game

Define the Game class as the Originator, representing the game whose state needs saving.

class Game:
def __init__(self):
self._state = None
# Adding GameLevel attribute for state change demonstration
self._level = 1

def play(self):
# Simulate game progress by increasing the game level
self._level += 1
print(f"Advanced to level {self._level}")

def create_memento(self):
# Storing the current game level as part of the state
return GameState({"level": self._level})

def restore(self, memento):
self._state = memento.get_state()
# Retrieving the stored game level
self._level = self._state.get("level", 1)

Step 2: Memento GameState

Create the GameState class as the Memento, capturing and storing the game’s state.

class GameState:
def __init__(self, state):
self._state = state

def get_state(self):
return self._state

Step 3: Caretaker GameSaver

Implement the GameSaver class as the Caretaker, managing game saves.

class GameSaver:
def __init__(self):
self._saves = {}

def save_game(self, name, state):
self._saves[name] = state

def load_game(self, name):
return self._saves[name]

Client Code

Demonstrate the usage of the Memento pattern.

if __name__ == "__main__":
# Instantiate classes
game = Game()
game_saver = GameSaver()

# Play the game and save state
game.play()
saved_state = game.create_memento()
game_saver.save_game("save_1", saved_state)

# Advance game further
game.play()

# Load and restore saved state
loaded_state = game_saver.load_game("save_1")
game.restore(loaded_state)

# Check restored state
print(f"Restored to level {game._level}")

10 Real-World Use Cases for Memento Pattern

Memento finds application in various software, frameworks, and libraries across different domains:

  1. Version Control Systems (e.g., Git): Employs Memento for managing commit history, enabling users to navigate and revert to previous versions of their codebases.
  2. Text Editors (e.g., Microsoft Word): Utilizes Memento for undo/redo functionalities, allowing users to revert to previous document states.
  3. Graphic Design Software (e.g., Adobe Photoshop): Uses Memento to enable the undo/redo feature for reverting changes made to designs or layers.
  4. Gaming Engines (e.g., Unity): Implements Memento for managing game states, enabling players to revert to specific checkpoints or saved states within a game.
  5. Database Management Systems (e.g., PostgreSQL): Utilizes Memento for transactional rollback, allowing database systems to revert to previous consistent states in case of errors.
  6. Content Management Systems (e.g., WordPress): Employs Memento for managing content revisions, allowing users to revert to previous versions of published content.
  7. Virtual Machines (e.g., VMware): Employs Memento for snapshot functionalities, enabling users to save and revert virtual machine states.
  8. E-commerce Platforms (e.g., Shopify): Leverages Memento for managing product changes, allowing merchants to revert to previous product configurations.
  9. Software Development Tools (e.g., Jira): Utilizes Memento for managing issue histories, enabling developers to track and revert issue changes.
  10. Configuration Management Tools (e.g., Ansible): Utilizes Memento for managing configuration changes, enabling administrators to revert system configurations.
Dall-E generated image with the following concept: Infinite Mirror, reflecting into an infinite number of smaller versions, illustrating the continuous preservation and restoration cycle.

Best Practices and Considerations

Pros

  1. Preserving Encapsulation: Snapshots of object states can be taken without breaching encapsulation, preserving data integrity.
  2. Code Simplification: Allowing caretakers to manage state history simplifies the originator’s code, enhancing readability.

Cons

  1. Memory Overhead: Frequent creation of mementos might lead to increased RAM consumption, impacting system resources.
  2. Lifecycle Management: Caretakers need to monitor the originator’s lifecycle to discard obsolete mementos effectively.
  3. State Integrity: Dynamic languages like Python, PHP, and JavaScript can’t guarantee untouched memento states due to their dynamic nature.

Best Practices

  1. Clear Abstractions: Maintain clear and concise abstractions to ensure a cohesive and understandable interface.
  2. Interface Utilization: Implement interfaces to enforce consistency among components interacting with mementos.

Considerations

  1. Complexity Management: Handling large-scale mementos might introduce complexity, necessitating effective hierarchical structuring.
  2. Runtime Efficiency: Recursive operations within mementos might impact system performance, requiring optimization strategies for efficiency.

Memento Pattern’s Relations with Other Patterns

The Memento pattern aligns with other patterns like Command and Iterator for specific functionalities, while also standing apart due to its focus on state capture and restoration.

Comparison with Command Pattern

Collaborate for “undo” operations. Commands execute various operations on an object, while Mementos save the object’s state before command execution.

Relationship with Iterator Pattern

Work together to capture and potentially revert the current iteration state. Memento aids in saving and restoring the iteration state if needed.

Contrast with Prototype Pattern

Sometimes, a Prototype serves as a simpler alternative to Memento. It’s viable for storing straightforward object states without complex external links.

Comparison with State Pattern

Both patterns aim to manage object states, but their approaches differ. Memento captures and stores an object’s state for restoration, primarily used for “snapshot” functionality.

In contrast, the State pattern focuses on allowing an object to change its behavior as its internal state changes.

Conclusion

In summary, the Memento Design Pattern emerges as a valuable asset for preserving and restoring object states in software systems.

By enabling the creation of snapshots of object states without compromising encapsulation, it facilitates easy restoration when needed. This pattern simplifies the handling of state histories, enhancing code readability and maintainability.

Hopefully, this exploration into the Memento pattern proves insightful for your software development endeavors, Happy coding! 👩‍💻

Next on the Series 🚀

Read More 📜

The Series 🧭

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software (Book)
  2. refactoring.guru Memento
  3. Head First Design Patterns (Book)
  4. Memento Design Pattern in Java
  5. sourcemaking Memento Design Pattern
  6. Memento pattern

--

--

Amir Lavasani

I delve into machine learning 🤖 and software architecture 🏰 to enhance my expertise while sharing insights with Medium readers. 📃