Discover the Memento Pattern – a design pattern that enables safe storage and restoration of object states, powering features like Undo/Redo in professional applications.
No table of contents available for this article
Category: Behavioral Pattern
Purpose: The Memento Pattern allows you to save and restore previous versions of an object without exposing its internal structure.
Think of Memento as an automatic journal – whenever something important changes, it records the entire current state so you can "travel back in time" whenever needed.
Imagine you're building a text editor packed with features: text editing, font formatting, image insertion, and more.
To enhance user experience, you want to add Undo/Redo functionality – allowing users to reverse or repeat any action. The initial idea seems straightforward: before each operation, save the entire editor state to a "storage" (history). When users hit Undo, simply retrieve the most recent state and restore it.
Sounds easy, but reality tells a different story.
The Challenge:
To capture a "snapshot" of the editor's state, you need access to all its fields. However, in object-oriented programming, most important fields are marked as private – inaccessible from outside the class.
You might think: "Just make all fields public!" But this approach completely violates the Encapsulation principle – one of OOP's fundamental pillars. Your object becomes fragile and vulnerable to unintended modifications.
Another approach is creating a dedicated class to copy the entire editor object. But a new problem emerges: every time the editor's structure changes, this copy class must change too – creating tight coupling and maintenance nightmares.
Memento Pattern solves this elegantly: delegate snapshot creation to the object that owns the state (called the Originator).
Why does this work? Because that object has full access to all its own fields – including private ones. No encapsulation violations, no need to expose anything.
The copied state is stored in a special object called a Memento. Here's the clever part: Memento's content is "locked" – only the Originator can access it. Other objects can only retrieve metadata (like creation time, action name) through a restricted interface.
The Memento Pattern consists of three main components:
Originator This is the main class – the object whose state you want to save and restore. The Originator has two key responsibilities: creating snapshots from its current state and restoring state from saved snapshots.
Example: In a text editor, the Originator is the Editor class containing document content, cursor position, current font, etc.
Memento An object that stores a "copy" of the Originator's state at a specific moment. Mementos are typically designed as immutable – data is passed in only once through the constructor.
This ensures integrity: once created, no one can tamper with the snapshot.
Caretaker Acts as a "librarian" – managing the list of Mementos. The Caretaker knows when to create snapshots and when to restore, but doesn't care about (and can't access) what's inside the Memento.
Typically, the Caretaker stores Mementos in a stack. For Undo operations, it pops the top Memento and hands it to the Originator for restoration.
Relationship Between Components:
With this design, Memento is often nested inside the Originator. This allows the Originator to access Memento's private fields, while the Caretaker can only "hold" the Memento without "opening" it.
Preserves Encapsulation: Memento Pattern saves state without exposing private fields to the outside world. The object maintains its internal secrets.
Clear Separation of Concerns: The Originator handles snapshot creation and restoration. The Caretaker manages history. Each class has a single responsibility, making code cleaner and more maintainable.
Easy to Extend: You can add different snapshot types or change storage strategies without affecting the Originator.
Memory Consumption: If the application creates too many Mementos (e.g., saving a snapshot after every keystroke), memory will quickly be exhausted. You need strategies to limit snapshot count or clean up old ones.
Complex Lifecycle Management: The Caretaker must track the Originator's lifecycle to know when to dispose of unused Mementos, avoiding memory leaks.
Difficult to Guarantee Immutability: With dynamic languages like Python, JavaScript, or PHP, ensuring no one "sneakily" modifies Memento content is challenging. Additional conventions or techniques are needed for protection.
Undo/Redo Features: The classic use case. Any application requiring action reversal can apply Memento.
Transaction Management: In systems needing rollback (like database transactions), Memento saves state before executing a series of operations. If errors occur midway, you can restore to the initial state.
Game Development: Save checkpoints, game saves, or allow players to "reload" from a previous point.
Form Wizards: In multi-step forms, allow users to go back to previous steps without losing entered data.
Build a simple game with 3 levels. Players have 3 lives (lifelines) to complete the game. If a player loses a life at a higher level, the game restores to the last completed level instead of restarting from scratch.
Memento Pattern helps save state (checkpoint) after each level and restore when needed.
csharp
public class Memento
{
public int Level { get; }
public int Score { get; }
public string Health { get; }
public Memento(int level, int score, string health)
{
this.Level = level;
this.Score = score;
this.Health = health;
}
}The Memento stores three pieces of information: current level, score, and health. Properties only have getters, ensuring data cannot be modified after creation.
csharp
public class Player
{
public int Level { get; set; }
public int Score { get; set; }
public string Health { get; set; }
public int Lifeline { get; set; } = 3;
public Memento CreateMarker()
{
return new Memento(this.Level, this.Score, this.Health);
}
public void RestoreLevel(Memento playerMemento)
{
this.Level = playerMemento.Level;
this.Score = playerMemento.Score;
this.Health = playerMemento.Health;
this.Lifeline -= 1;
}
public void DisplayPlayerInfo()
{
Console.WriteLine($"Level: {this.Level}");
Console.WriteLine($"Score: {this.Score}");
Console.WriteLine($"Health: {this.Health}");
Console.WriteLine($"Lifeline left: {this.Lifeline}");
}
}Player is the Originator – capable of creating Mementos from its own state and restoring from saved Mementos.
csharp
public class CareTaker
{
public Memento LevelMarker { get; set; }
}The Caretaker simply stores the Memento. In practice, you might use a Stack to store multiple checkpoints.
csharp
class Program
{
static void Main(string[] args)
{
// Player completes level 1
Player player = new Player();
player.Level = 1;
player.Score = 100;
player.Health = "100%";
Console.WriteLine("--- Player info after completing Level 1 ---");
player.DisplayPlayerInfo();
// Create checkpoint after completing level
CareTaker careTaker = new CareTaker();
careTaker.LevelMarker = player.CreateMarker();
Thread.Sleep(2000);
// Player progresses to level 2
player.Level = 2;
player.Score = 130;
player.Health = "80%";
Console.WriteLine("\n--- Player info at Level 2 ---");
player.DisplayPlayerInfo();
// Player loses a life -> Restore to level 1
Console.WriteLine("\n[!] Player lost a life! Restoring checkpoint...");
player.RestoreLevel(careTaker.LevelMarker);
Console.WriteLine("\n--- Player info after restoration ---");
player.DisplayPlayerInfo();
Console.ReadLine();
}
}
```
**Output:**
```
--- Player info after completing Level 1 ---
Level: 1
Score: 100
Health: 100%
Lifeline left: 3
--- Player info at Level 2 ---
Level: 2
Score: 130
Health: 80%
Lifeline left: 3
[!] Player lost a life! Restoring checkpoint...
--- Player info after restoration ---
Level: 1
Score: 100
Health: 100%
Lifeline left: 2Command + Memento: A perfect combination for Undo features. Command executes operations while Memento saves state before each operation. To Undo, reverse the Command and restore the Memento.
Iterator + Memento: Save the Iterator's state (current position in collection) to return to that position later.
Prototype as Alternative: If the object to save is fairly simple without complex external resource links, Prototype (deep copy) can be a simpler solution than Memento.
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns