@ardalis Level: Intermediate
Table of Contents
- Course Overview
- Introduction to the Memento pattern
- Examples of problems solved by Memento
- Memento pattern structure
- 7.1 The base game: HangmanGame (without Memento)
- 7.2 Added Memento: HangmanMemento
- 7.3 The Originator enriched: HangmanGameWithUndo
- 7.4 The Caretaker: the application console
- 7.5 Encapsulation demonstration
1. Course Overview
This course, presented by Steve Smith (known as Ardalis), is a comprehensive introduction to the Memento pattern in C#. Steve Smith is an experienced .NET developer, architect, and trainer. He can be reached on Twitter, GitHub and YouTube under the handle @ardalis, and publishes short development tips on his podcast weeklydevtips.com.
What this course covers
- What problem is the Memento pattern designed to solve?
- What software design principles can be applied with this pattern?
- How to apply the Memento pattern in different ways in your applications?
- How to implement undo / redo logic using this pattern?
- What other design patterns are similar or related to Memento?
Learning Outcome
At the end of this course, you will be able to:
- Recognize situations where the Memento pattern is appropriate
- Apply this pattern with confidence in your C# applications
2. Introduction to the Memento pattern
Definition
A Memento maintains the internal state of an object so that that object can be restored to that state later.
The pattern is also sometimes called token pattern. It can be compared to:
- A save point in a video game
- A special commit in a source control system (like Git)
Category
The Memento pattern is classified as a behavioral pattern because it can be used to add an undo or replay behavior to an application.
Questions this course answers
| Question | Answer covered |
|---|---|
| What is the Memento pattern? | Module 2 |
| What problems does it solve? | Module 2-3 |
| How is it structured? | Module 4 |
| How to apply it in code? | Modules 6-7 |
| How to recognize similar patterns? | Modules 9-10 |
3. Examples of problems solved by Memento
3.1 Save state in games
This is one of the most classic applications of the Memento pattern. Video games often need to allow players to save the game state to resume later. Memento is perfectly suited to this scenario because:
- Full game state can be captured at a specific time
- This savepoint can be restored on demand
3.2 Support for undo in drawing or editing applications
Memento is an excellent candidate for implementing undo functionality in applications such as:
- Graphic editors (Adobe Photoshop, vector design)
- Text editors
- Data processing applications
Beyond simple undo, it is relatively easy to implement unlimited undo/redo with this pattern.
3.3 Rollback of distributed transactions
In distributed systems, where multiple services each have their own state, Memento can be used to standardize rollback. If one of the systems fails, all must revert to their previous state. Memento achieves this in a standardized way.
3.4 When to use this pattern?
You should consider using the Memento pattern in the following cases:
- Rollback: you need to be able to go back on the state of one or more objects
- Undo: your application requires an undo functionality (very common)
- Encapsulation: you have complex objects whose internal state you do not want to expose for rollback
Note: For simple scenarios involving primitive types like strings, Memento might be overkill. But if you have complex objects, rolling back their state often requires exposing that internal state to other collaborators — which would violate encapsulation and make the code more prone to bugs.
3.5 Link to SOLID principles
The Memento pattern allows you to:
- Respect the Single Responsibility Principle (SRP): state management is not a responsibility of the source object, it is delegated to a
Caretaker - Preserve encapsulation: the internal state of the object remains protected from inappropriate external access
In summary, the Memento pattern describes a way to capture the internal state of objects without violating encapsulation or the Single Responsibility Principle.
4. Structure of the Memento pattern
4.1 The three actors: Originator, Caretaker, Memento
The Memento pattern involves three objects that collaborate together:
The Originator
The Originator is the object whose state is being tracked. It could be:
- Of a game (
Game) - From a document (
Document) - A drawing canvas (
DrawingCanvas) - Of a business process
The state of this object can be simple or complex, but it must remain private and encapsulated from the rest of the application.
To support the Memento pattern, the Originator must interact with Mementos in two ways:
- Create a Memento from its current state:
CreateMemento()method (orSaveSetPoint(),CreateBackup(), etc.) - Restore its state from a provided Memento: method
SetMemento(memento)(orResumeFrom(),RestoreFrom(), etc.)
The Caretaker
The Caretaker is the external object that interacts with the Originator. It could be:
- A user interface (UI)
- From a web page element
- From a console application
- From the main
Program
Its responsibility is twofold:
- Perform operations on the Originator
- Manage Originator Mementos (storage, organization, undo/redo invocation)
The Caretaker can keep Mementos in memory or in a persistent store if serialization is supported.
The Memento
The Memento is the object that contains the internal state of the Originator. It represents:
- A game save
- A saved version of a document or drawing
It must contain the complete state of the Originator, so that it can be fully restored from the Memento.
4.2 Class diagram
+-------------------+ +-------------------+
| Originator | | Caretaker |
+-------------------+ +-------------------+
| - state |<-------->| - mementos |
+-------------------+ +-------------------+
| + CreateMemento() | | + SaveState() |
| + SetMemento(m) | | + Undo() |
+-------------------+ | + Redo() |
| +-------------------+
| crée/lit
v
+-------------------+
| Memento |
+-------------------+
| ~ state |
+-------------------+
| ~ GetState() |
| ~ SetState() |
+-------------------+
Interaction flow:
- User requests to save state
- The Caretaker calls
CreateMemento()on the Originator - The Originator creates a new Memento instance, copies its state to it and returns it to the Caretaker
- Later, user requests a restore
- The Caretaker calls
SetMemento(memento)on the Originator with the chosen Memento - The Originator retrieves the state of the Memento via
GetState()and updates its current state
4.3 Key design points
| Rule | Explanation |
|---|---|
| The Memento must be simple | It’s a value object with very little logic: mostly internal state and property accessors. May also have serialization attributes if necessary. |
| Methods on the Originator | Add CreateMemento() and SetMemento() (or equivalent names) |
| Separate liability | The storage and management of Mementos belongs to the Caretaker, generally associated with the UI |
| Preserve encapsulation | Avoid giving the Caretaker or any other object (except the Originator) direct access to the internal state of the Memento |
| Accessibility in C# | C# does not have a friend level of protection like C++. To restrict access, use internal and place the Originator and Memento in the same project (assembly) |
5. Implementing Undo/Redo with Memento
5.1 Undo only — using a stack
The most common way to manage Mementos in Caretaker is to maintain a Stack of previous states to support multiple undo operations.
Viewing the undo stack
Let’s imagine that the application has gone through a series of updates:
États successifs :
État 1 → État 2 → État 3 → État 4 (courant)
Pile Undo (du bas vers le haut) :
[ État 1 | État 2 | État 3 ]
État courant = État 4
Undo operation:
Avant :
Pile Undo : [ État 1 | État 2 | État 3 ]
État courant = État 4
Après Undo :
Pile Undo : [ État 1 | État 2 ]
État courant = État 3 ← (popped de la pile)
État 4 est PERDU (dans un undo sans redo)
In an application that only supports undo, there is no way to recover State 4 once undo; it is simply replaced by the previous state.
The user can continue to undo: State 3 → State 2 → State 1, or continue forward by changing the state (which pushes State 3 onto the stack and replaces the current state with a new State 4).
5.2 Undo AND Redo — two stacks
To support redo, the only real difference is that during an undo, instead of simply overwriting the current state with the previous one, we push the current state onto the redo stack before overwriting it.
Visualization with undo stack + redo stack
État initial :
Pile Undo : [ État 1 | État 2 | État 3 ]
Pile Redo : [ ]
État courant = État 4
Après Undo :
Pile Undo : [ État 1 | État 2 ]
Pile Redo : [ État 4 ] ← État 4 poussé sur redo
État courant = État 3
Après second Undo :
Pile Undo : [ État 1 ]
Pile Redo : [ État 4 | État 3 ]
État courant = État 2
Après Redo :
Pile Undo : [ État 1 | État 2 ] ← État 2 retourné sur undo
Pile Redo : [ État 4 ]
État courant = État 3 ← popped de la pile redo
5.3 Detailed implementation steps
To implement undo/redo logic in your application:
1. Initial setup:
- The Caretaker is responsible for the undo stack and (optionally) the redo stack
2. At each change of state:
- The Caretaker retrieves the state before the change (via
CreateMemento()) - He pushes this Memento onto the undo pile
3. Operation Undo:
- Pop previous Memento from stack undo
- Call
SetMemento()on the Originator with this Memento - If redo is supported, push current state to redo stack before restoring
4. Operation Redo:
- Push current state to stack undo (like any other action)
- Pop the Memento from the redo stack and call
SetMemento()to restore
Important reminder: Mementos must be immutable value objects with state but no behavior. Only the Originator who created the Memento should be able to assign or retrieve its state.
Pseudo-code of undo/redo logic
// Dans le Caretaker
Stack<IMemento> _undoStack = new Stack<IMemento>();
Stack<IMemento> _redoStack = new Stack<IMemento>();
// Avant chaque action
void BeforeAction()
{
_undoStack.Push(_originator.CreateMemento());
_redoStack.Clear(); // Effacer le redo quand une nouvelle action est faite
}
// Opération Undo
void Undo()
{
if (_undoStack.Count == 0) return;
_redoStack.Push(_originator.CreateMemento()); // Sauvegarder pour redo
_originator.SetMemento(_undoStack.Pop());
}
// Opération Redo
void Redo()
{
if (_redoStack.Count == 0) return;
_undoStack.Push(_originator.CreateMemento()); // Sauvegarder pour undo
_originator.SetMemento(_redoStack.Pop());
}
6. Apply Memento to existing code
If you have existing code that could benefit from the Memento pattern (for example because you need savepoints or undo/redo behavior), here are the steps to refactor it.
Refactoring steps
Step 1: Refactor carefully If you have already implemented the state management logic directly in the class that owns the state (the Originator), start by refactoring its design according to good refactoring practices, so as not to introduce bugs.
Step 2: Define a Memento type
Define a Memento type to store the state of the Originator. Keep it as simple as possible. Try to ensure that its accessors are only available to the Originator to preserve encapsulation.
Step 3: Add methods to the Originator Add methods on the Originator to:
- Get a Memento: typically called
SaveMemento(),CreateMemento(),CreateSetPoint(),CreateBackup() - Restore from a Memento: typically called
SetMemento(),Restore(),ResumeFrom()
Step 4: Manage Mementos in Caretaker Identify the class that will be responsible for managing Mementos (the Caretaker). Create the data structures necessary to store one or more Mementos, such as stacks for undo and, optionally, redo.
Summary in checklist form
✅ Identifier l'Originator (l'objet dont l'état doit être suivi)
✅ Créer la classe Memento (simple, avec état mais sans logique)
✅ Restreindre l'accès au Memento (internal en C#, même assembly)
✅ Ajouter CreateMemento() à l'Originator
✅ Ajouter SetMemento() à l'Originator
✅ Identifier le Caretaker (souvent l'UI ou le programme principal)
✅ Ajouter Stack<Memento> undoStack au Caretaker
✅ (Optionnel) Ajouter Stack<Memento> redoStack pour le redo
✅ Sauvegarder l'état avant chaque action dans le Caretaker
✅ Implémenter la logique Undo() dans le Caretaker
✅ (Optionnel) Implémenter la logique Redo() dans le Caretaker
7. Demo: Adding Undo support with Memento
This demonstration illustrates how to implement the Memento pattern in a simple C# console game: the Hangman game.
The Hangman Game — Rules reminder
In the Hangman game:
- There is a secret word to guess
- Player must guess the word letter by letter
- He has a limited number of bad attempts before losing
- If the player guesses all the letters before exhausting his attempts, he wins
7.1 The base game: HangmanGame (without Memento)
Here is the structure of the HangmanGame class as described in the demonstration:
// HangmanGame.cs
// ORIGINATOR (version de base, sans Memento)
public class HangmanGame
{
private readonly string _secretWord;
private readonly int _initialGuesses;
protected List<char> _guesses = new List<char>();
protected int _guessesRemaining;
public bool IsOver => Won || Lost;
public bool Won => MaskedWord == _secretWord;
public bool Lost => _guessesRemaining <= 0;
public int GuessesRemaining => _guessesRemaining;
public IEnumerable<char> PreviousGuesses => _guesses;
public HangmanGame(string secretWord, int initialGuesses = 5)
{
_secretWord = secretWord;
_initialGuesses = initialGuesses;
_guessesRemaining = initialGuesses;
}
/// <summary>
/// Affiche le mot avec des underscores pour les lettres non encore devinées
/// </summary>
public string MaskedWord
{
get
{
var masked = new System.Text.StringBuilder();
foreach (char c in _secretWord)
{
masked.Append(_guesses.Contains(c) ? c : '_');
}
return masked.ToString();
}
}
/// <summary>
/// Logique principale : tenter de deviner une lettre
/// </summary>
public GuessResult Guess(char letter)
{
// Validation : doit être une lettre A-Z
if (!char.IsLetter(letter))
return GuessResult.InvalidGuess;
letter = char.ToLower(letter);
// Vérification des doublons
if (_guesses.Contains(letter))
return GuessResult.DuplicateGuess;
// Ajouter à la liste des tentatives
_guesses.Add(letter);
// Vérifier si la lettre est dans le mot secret
if (_secretWord.Contains(letter))
{
// Vérifier si le mot est entièrement deviné (victoire)
if (MaskedWord == _secretWord)
return GuessResult.Won;
return GuessResult.CorrectGuess;
}
// La lettre n'est pas dans le mot : décrémenter le compteur
_guessesRemaining--;
if (_guessesRemaining <= 0)
return GuessResult.Lost;
return GuessResult.WrongGuess;
}
}
public enum GuessResult
{
InvalidGuess,
DuplicateGuess,
CorrectGuess,
WrongGuess,
Won,
Lost
}
The main game loop (before Memento)
// Program.cs (version sans Memento — Caretaker basique)
class Program
{
static void Main(string[] args)
{
var game = new HangmanGame("secret");
while (!game.IsOver)
{
Console.WriteLine("Welcome to Hangman!");
Console.WriteLine($"Previous guesses ({game.PreviousGuesses.Count()}): " +
$"{string.Join(", ", game.PreviousGuesses)}");
Console.WriteLine($"Word: {game.MaskedWord}");
Console.WriteLine($"Guesses remaining: {game.GuessesRemaining}");
Console.Write("Make a guess (A-Z): ");
var input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) continue;
var result = game.Guess(input[0]);
if (result == GuessResult.WrongGuess)
{
Console.WriteLine("Wrong guess!");
}
}
if (game.Won)
Console.WriteLine($"You won! The word was: {game.MaskedWord}");
if (game.Lost)
Console.WriteLine("You lost!");
}
}
Example of game session (without undo):
Welcome to Hangman!
Word: ______ (6 lettres)
Guesses remaining: 5
Make a guess: A → Wrong
Make a guess: B → Wrong (4 restant)
Make a guess: C → Correct! (___c_t)
Make a guess: D → Wrong (2 restant)
Make a guess: E → Correct! (__e__t) → 2 E's
Make a guess: F → Wrong (1 restant)
Make a guess: G → Wrong → LOST!
This is where we would like to be able to cancel certain bad attempts…
7.2 Added Memento: HangmanMemento
The Memento must be as simple as possible. It only contains the internal state needed to restore the game, without any business logic.
// HangmanMemento.cs
// MEMENTO — value object simple, état encapsulé
public class HangmanMemento
{
// Marqué 'internal' pour restreindre l'accès au même assembly
// Ainsi, le Caretaker (dans un autre projet) ne peut PAS accéder à Guesses
internal char[] Guesses { get; }
// Le constructeur peut aussi être internal si on ne veut pas
// que le Caretaker puisse créer des Mementos directement
public HangmanMemento(char[] guesses)
{
Guesses = guesses;
}
}
Important Notes:
- The
Guessesproperty is markedinternal: it is only accessible from the same assembly (project) - The
Caretaker(which is in a different project) can create and keep Mementos, but cannot access their contents - This restriction reinforces encapsulation: the internal state of the Memento can only be read by the
Originatorwho created it
7.3 The Originator enriched: HangmanGameWithUndo
To keep the changes clearly visible, HangmanGameWithUndo inherits from HangmanGame and adds only the two methods needed to support Memento.
// HangmanGameWithUndo.cs
// ORIGINATOR avec support Memento — hérite de HangmanGame
public class HangmanGameWithUndo : HangmanGame
{
public HangmanGameWithUndo(string secretWord, int initialGuesses = 5)
: base(secretWord, initialGuesses)
{
}
/// <summary>
/// CreateMemento — Crée un SetPoint (point de sauvegarde) représentant
/// l'état courant du jeu.
/// Convertit la liste des tentatives en tableau pour en faire une COPIE.
/// </summary>
public HangmanMemento CreateSetPoint()
{
// Important : convertir en tableau pour avoir une copie indépendante
// Si on passait la référence à _guesses, le Memento changerait avec le jeu!
return new HangmanMemento(_guesses.ToArray());
}
/// <summary>
/// SetMemento — Restaure l'état du jeu à partir d'un SetPoint.
/// Remplace les tentatives actuelles par celles contenues dans le Memento.
/// </summary>
public void ResumeFrom(HangmanMemento memento)
{
_guesses = memento.Guesses.ToList();
}
}
Why _guesses.ToArray()?
It is crucial to create a copy of the list when creating the Memento. If we simply passed the reference to _guesses, the Memento would point to the same object as the game, and its contents would change with each new attempt — which would render the Memento useless.
7.4 The Caretaker: the application console
The Caretaker is the Program (the console application). He is responsible for:
- Create and store Mementos in a
Stack<HangmanMemento> - Save state before each attempt
- Handle the
'-'command to trigger undo
// Program.cs (version avec Memento — CARETAKER)
class Program
{
static void Main(string[] args)
{
// Originator : utiliser HangmanGameWithUndo au lieu de HangmanGame
var game = new HangmanGameWithUndo("secret");
// Caretaker : pile pour l'historique du jeu (undo stack)
var gameHistory = new Stack<HangmanMemento>();
// IMPORTANT : Sauvegarder l'état initial AVANT de démarrer la boucle
// Ainsi on peut undo jusqu'au tout début
gameHistory.Push(game.CreateSetPoint());
while (!game.IsOver)
{
Console.WriteLine("Welcome to Hangman!");
Console.WriteLine($"Previous guesses ({game.PreviousGuesses.Count()}): " +
$"{string.Join(", ", game.PreviousGuesses)}");
Console.WriteLine($"Word: {game.MaskedWord}");
Console.WriteLine($"Guesses remaining: {game.GuessesRemaining}");
// Nouvelle option : '-' pour undo
Console.Write("Make a guess (A-Z) or '-' to undo: ");
var input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) continue;
var entry = input[0];
// Gestion du undo — UNIQUEMENT dans le Caretaker
// L'Originator (HangmanGame) ne sait RIEN de l'undo
if (entry == '-')
{
if (gameHistory.Count > 0)
{
// Retirer le dernier état sauvegardé de la pile
// et restaurer le jeu à cet état
game.ResumeFrom(gameHistory.Pop());
}
// Continuer la boucle sans passer '-' au jeu
continue;
}
// Tentative normale : sauvegarder l'état APRÈS la tentative
var result = game.Guess(entry);
// Sauvegarder le nouvel état après chaque tentative valide
// (le Caretaker pousse sur la pile après chaque action)
gameHistory.Push(game.CreateSetPoint());
if (result == GuessResult.WrongGuess)
{
Console.WriteLine("Wrong guess!");
}
}
if (game.Won)
Console.WriteLine($"You won! The word was: {game.MaskedWord}");
if (game.Lost)
Console.WriteLine("You lost!");
}
}
Caretaker Important Points:
| Item | Description |
|---|---|
| Only the Caretaker manages the stack | The Originator has no knowledge of the stack or the concept of undo |
| Initial backup | The first Push takes place before the game even starts |
| Undo invisible to the Originator | The '-' command is processed entirely in the Caretaker, it is never transmitted to the game |
| Save after each action | After each attempt, the state is pushed onto the stack |
Game session with Memento (full demo)
Welcome to Hangman!
Word: ______ (6 lettres)
Guesses remaining: 5
Make a guess (A-Z) or '-' to undo: A → Wrong (4 restant)
Make a guess: B → Wrong (3 restant)
Make a guess: C → Correct! (__e__t)
Make a guess: D → Wrong (2 restant)
Make a guess: E → Correct! (__ece_)
Make a guess: F → Wrong (1 restant)
→ On est en danger! Utilisons undo...
Make a guess: - → UNDO! (retour à 2 restant)
Make a guess: - → UNDO! (retour à 3 restant)
Make a guess: R → Correct! (__ecr_t)
Make a guess: - → UNDO de E (on avait un E, on l'a annulé par erreur)
Make a guess: E → Correct! (__ecre_)
Make a guess: S → Correct! (s_ecret)
Make a guess: T → Correct! (secret)
→ YOU WON! Le mot secret était "secret"!
Thanks to undo, the player can “cheat” by canceling bad attempts and ultimately win the game.
7.5 Encapsulation demonstration
One of the major advantages of the internal approach is that it is impossible for the Caretaker to access the internal state of the Memento.
// Dans Program.cs (le Caretaker) — Tentative d'accès illégal
var firstMemento = gameHistory.First();
// ❌ ERREUR DE COMPILATION :
// "The property 'HangmanMemento.Guesses' is inaccessible due to its protection level"
var guesses = firstMemento.Guesses; // Interdit !
// ✅ Le Caretaker peut seulement :
// - Créer des Mementos via game.CreateSetPoint()
// - Les conserver dans sa pile
// - Les passer à game.ResumeFrom() pour restaurer l'état
// Il NE PEUT PAS lire ou modifier le contenu du Memento directement
This restriction ensures that:
- The Caretaker cannot access the internal state of the Memento, just as it cannot access the internal state of the game itself
- Encapsulation is fully respected
- Only the Originator can read and write to the Memento
8. Analysis of the result
After implementing the Memento pattern in the Hangman game example, here are the important observations:
Role of the Caretaker
- The Caretaker is the application console / user interface
- Only UI needs to keep Mementos
- There are no collections or internal fields with a Memento in the Originator (the
HangmanGame)
Pattern limitations
It is worth remembering that the Memento pattern may not be appropriate if the state is very large. In the example, we only store individual characters, but if we stored:
- The state of a large game world
- A very detailed document
- A video
…we might not be able to use this pattern (too much memory required for each Memento).
Encapsulation in C# vs other languages
Different languages offer different capabilities when it comes to object protection:
| Language | Mechanism |
|---|---|
| C++ | Keyword friend — selective access between classes |
| C# | No friend — use internal + separate projects |
| Java | Package-private — classes in the same package |
Recommended approach in C#:
- Place the Originator and the Memento in the same project (same assembly)
- Mark Memento accessors as
internal - Ensure that the Caretaker belongs to a different project (another assembly)
Solution Architecture:
├── HangmanGame.csproj (Assembly A)
│ ├── HangmanGame.cs → Originator
│ └── HangmanMemento.cs → Memento (internal Guesses)
│
└── HangmanConsole.csproj (Assembly B — référence A)
└── Program.cs → Caretaker (ne peut pas accéder à Guesses)
9. Alternative approaches
9.1 Store inverse operations (pattern Command)
An alternative approach to the Memento pattern is to store inverse operations rather than the entire state of the system. This approach works very well with the Command pattern, because each command can have a known opposing command capable of reversing its effects.
Principle:
- Instead of storing the state, we store the operations themselves
- Undo is accomplished by applying the reverse operations for each command
Example: a simple calculator (favorable case)
Départ : 30
+ 10 : 40
× 2 : 80
Undo × 2 (opération inverse : ÷ 2) → 40
Undo + 10 (opération inverse : - 10) → 30 ✅ Correct !
This works perfectly because the operations are fully reversible.
Example: non-reversible operation (unfavorable case)
Départ : 10
- 20 : -10
² (au carré) : 100
Undo ² (inverse : √)
→ En mathématiques : ±10
→ En logiciel : +10 seulement ❌ (pas de ±)
Undo - 20 (inverse : + 20)
→ 10 + 20 = 30 ❌ (résultat attendu : 10)
Conclusion: This approach only works for operations that have inverses that always return the original result.
Example: translation (non-reversible operation)
Anglais : "Pluralsight delivers quality training to developers worldwide."
↓ Traduire en Allemand
Allemand : "Pluralsight bietet Entwicklern weltweit qualitativ hochwertige Schulungen."
↓ Traduire en Russe
Russe : [texte en cyrillique]
↓ Undo : re-traduire en Allemand
Allemand : [résultat approximatif]
↓ Undo : re-traduire en Anglais
Anglais : "Pluralsight offers high quality developer training courses worldwide."
The final result is close to the original but clearly different — translation is not a perfectly reversible operation.
Memento vs Command comparison for undo
| Criterion | Memento (complete state) | Command (reverse operations) |
|---|---|---|
| Reliability | Always accurate | Only if operations are reversible |
| Memory | Higher (full state) | Lower (just the controls) |
| Complexity | Simple | Requires defining inverses |
| Applicability | Universal | Limited to reversible operations |
9.2 Store differences (diffs)
When storing operations and reversing them doesn’t work, another alternative to the Memento pattern that requires less space is to store only the differences rather than the full state.
This is exactly how version control systems like Git work.
Benefits of diffs
- We typically do not store as much information as for complete reports
- For small changes and short history this works very well
Disadvantages of diffs
- Computational resources for creation: Creating a diff may require more resources than simply making a copy of the current state
- Computational resources for restoration: Restoring a particular state may involve applying many diffs successively, which can be resource intensive
Comparison of the three approaches
| Approach | Memory space | Complexity | Reliability |
|---|---|---|---|
| Memento (complete state) | High | Low | High (always accurate) |
| Command (reverse operations) | Low | Average | Conditional |
| Diff (differences) | Medium | High | High (but restoration cost) |
10. Related Patterns
Two patterns are directly linked to Memento:
10.1 Pattern Command
The pattern Command can be used as an alternative to storing full states. Instead of the Caretaker managing states, it manages a series of commands. When the user wants to return to a previous state, reverse commands are applied.
When to choose Command over Memento?
- When all operations are easily reversible
- When the state is too large to copy completely
- When you already have an order-based model
When to choose Memento over Command?
- When certain operations are not reversible
- When restoration reliability is critical
- When the state is relatively compact
10.2 Pattern Iterator
The Iterator pattern may sometimes need to maintain state between iterations. Iterators can leverage the Memento pattern as a way to do this, keeping this logic outside the iterator itself.
This is an example of patterns that complement each other rather than replacing each other:
- The Iterator focuses on traversing a collection
- Memento keeps iteration state
- Combining the two keeps the Iterator simple and state is managed cleanly
11. Key Takeaways
Pattern summary
| Appearance | Detail |
|---|---|
| Name | Memento (also called Token) |
| Category | Behavioral patterns |
| Objective | Store the state of an Originator object to allow its later restoration |
| Delegated responsibility | State management is transferred from the Originator to the Caretaker |
| Common uses | Game save points, basic undo, full undo/redo |
SOLID principles applied
1. Single Responsibility Principle (SRP)
The pattern helps enforce the SRP by removing state management as a responsibility of the Originator. The Originator focuses on its business logic; The Caretaker focuses on state history management.
2. Encapsulation
Memento avoids violating encapsulation by keeping the internal state of the Originator protected from inappropriate access. Neither the Caretaker nor any other object should have access to the internal state of the Memento or Originator.
What you learned in this course
- ✅ How to recognize problems that the Memento pattern can solve
- ✅ The structure of the pattern with its three actors (Originator, Caretaker, Memento)
- ✅ How to refactor an existing system to apply the pattern
- ✅ How to implement undo and undo/redo with stacks
- ✅ Alternative approaches: storing commands or diffs instead of complete states
- ✅ Related patterns: Command and Iterator
Golden rules
- The Memento should be simple — just a state container, no logic
- Only the Originator must be able to read/write the internal state of the Memento
- Only the Caretaker must manage the Mementos collection
- Restrict access in C# with
internaland separate projects - Think about memory — if the state is very large, consider an alternative
- Mementos are immutable — once created, they should not change
12. References and resources
Source code
- GitHub: https://github.com/ardalis/DesignPatternsInCSharp
- Repository:
ardalis/DesignPatternsInCSharp - Key classes:
HangmanGame,HangmanGameWithUndo,HangmanMemento
Trainer
- Steve Smith (Ardalis)
- Website: ardalis.com
- Twitter/GitHub/YouTube: @ardalis
- Podcast: weeklydevtips.com
Related courses on Pluralsight
| Courses | Link with Memento |
|---|---|
| C# Design Patterns: Command | Alternative to Memento for undo via reverse operations |
| C# Design Patterns: Iterator | Can use Memento to maintain state |
| SOLID Principles of OO Development | SRP and encapsulation applied in Memento |
| Refactoring Fundamentals | Useful for applying Memento to existing code |
Glossary
| Term | Definition |
|---|---|
| Memento | Object that stores the internal state of an Originator at a specific time |
| Originator | Object whose state is tracked; creates and consumes Mementos |
| Caretaker | Object responsible for the conservation and management of Mementos |
| Token pattern | Another name for the Memento pattern |
| Undo stack | Stack of Mementos used to implement undo |
| Redo stack | Stack of Mementos used to implement repetition |
| SetPoint | Alternative name for a Memento in the context of a game |
| PRS | Single Responsibility Principle — each class has only one reason to change |
| Encapsulation | OO principle that protects the internal state of an object |
| internal | C# access modifier that restricts access to the same assembly |
| Stack | LIFO (Last In, First Out) Data Structure in C# |
| Value object | Object defined by its values rather than its identity; ideally immutable |
| Behavioral pattern | Category of design patterns that deal with communication and responsibilities between objects |
| SOLID | Acronym for five OO design principles (Single responsibility, Open-closed, Liskov substitution, Interface segregation, Dependency inversion) |
Search Terms
c-sharp · design · patterns · memento · testing · architecture · c# · .net · development · undo · pattern · caretaker · game · redo · command · diffs · originator · stack · approaches · case · comparison · encapsulation · non-reversible · operation