Memento Pattern: Saving and Restoring Object State
When building software, sometimes we need the ability to “travel back in time.” Whether it’s undoing the last user action, reverting a configuration change, or restoring a session to a previous state — that’s where the Memento Pattern shines.
The Memento Pattern lets you capture and store an object’s internal state without exposing its internal details. Later, you can restore the object to that exact state.
Real-World Analogy
Imagine writing a document in Microsoft Word. You type a few paragraphs, and then — oops! — you accidentally delete an important section. Luckily, you hit Ctrl + Z and Word magically restores it.
Here, Word is using the Memento Pattern under the hood:
- Originator: The document (it knows its own state).
- Memento: A saved snapshot of the document at a certain moment.
- Caretaker: The undo manager that keeps track of all saved states.
Real-World Backend Example
Let’s say you’re building a backend service that allows admins to update application configuration settings. To prevent accidental misconfigurations, you want the ability to rollback to a previous state.
Step-by-step:
Step 1 Memento: An object that stores a snapshot of settings.
class ConfigMemento: def __init__(self, state): self._state = deepcopy(state) def get_state(self): return deepcopy(self._state)
Step 2 Originator: The
class AppConfig: def __init__(self, settings): self._settings = settings def update_setting(self, key, value): self._settings[key] = value def create_memento(self): return ConfigMemento(self._settings) def restore_from_memento(self, memento): self._settings = memento.get_state() def __repr__(self): return f"AppConfig({self._settings})"
Step 3 Caretaker: A manager that keeps track of saved states and can restore them.
class ConfigHistory: def __init__(self): self._history = [] def save(self, memento): self._history.append(memento) def undo(self): if not self._history: return None return self._history.pop()
Example usage
if __name__ == "__main__": config = AppConfig({"debug": False, "max_connections": 100}) history = ConfigHistory() # Save initial state history.save(config.create_memento()) # Make some changes config.update_setting("debug", True) config.update_setting("max_connections", 200) print("Updated:", config) # Rollback to previous state config.restore_from_memento(history.undo()) print("Rolled back:", config)
Output:
Updated: AppConfig({'debug': True, 'max_connections': 200}) Rolled back: AppConfig({'debug': False, 'max_connections': 100})
Gotchas & Considerations
- Memory Usage
- Storing too many mementos can consume a lot of memory, especially if the state is large.
- Consider limiting history length or using a lightweight representation.
- Immutable Snapshots
- The memento object should be immutable to prevent accidental changes to saved states.
- Performance Trade-offs
- Deep copying large objects can be expensive.
- Use incremental or differential snapshots if performance is critical.
- Security Concerns
- Be careful when storing sensitive data in mementos (e.g., user credentials, API keys).
- Mask or exclude sensitive fields when creating mementos.
When to Use Memento
- Implementing undo/redo functionality in an application.
- Keeping track of previous states in a workflow system.
- Restoring previous configurations in backend services.
Final Thoughts
The Memento Pattern is a powerful tool when you need reversible changes without breaking encapsulation. It gives you the flexibility to store and restore state as needed, but it comes at a cost in terms of memory and performance. Use it wisely — limit how many snapshots you keep, avoid storing unnecessary data, and always think about the security of what you’re saving.
When done right, the Memento Pattern can be your application’s safety net, ensuring that mistakes don’t become disasters.