| Type | Behavioral |
| Purpose | Without violating encapsulation, capture and externalize an object's internal state so that it can be restored to this state later. |
Description
The Memento pattern captures an object's internal state and stores it externally so that the object can be returned to that state later. This allows state to be saved and restored without violating encapsulation.
Overview
| Originator | An object that has state to be remembered. |
| Memento | Stores a snapshot of the Originator's internal state. |
| Caretaker | External object that manages mementos. |
Implementation
The code is divided into the originator (Canvas), the Memento, and the caretaker (MementoDemo) which follows the foundational principles of the pattern.
- Canvas, as the originator, is responsible for creating mementos and restoring its state from them.
- The Memento class, nested within Canvas, is essentially a snapshot of the state of a Canvas object. This encapsulates the state within the memento, protecting it from external modifications.
- MementoDemo as the caretaker manages the lifecycle of mementos. It keeps track of existing mementos, can request new mementos from the originator and can request that the originator restore a previous state.
The IMemento interface allows the caretaker to store mementos without knowing the private Memento type nested inside Canvas. The concrete memento remains an implementation detail of the originator. Other implementations may represent mementos differently; an interface is not a requirement of the pattern.
public interface IMemento
{
string GetState();
}
// Originator
public class Canvas
{
// Memento
private class Memento(string state) : IMemento
{
public string GetState() => state;
}
// implementation of Originator continues...
private const int Width = 10;
private const int Height = 4;
private readonly string _line = new('*', Width + 2);
private char[] _data = new string(' ', Width * Height).ToCharArray();
public void Plot(int x, int y, char character)
{
--x;
--y;
_data[y * Width + x] = character;
}
public void Draw()
{
Console.WriteLine(_line);
for (var y = 0; y < Height; ++y)
{
Console.Write('*');
for (var x = 0; x < Width; ++x)
{
Console.Write(_data[y * Width + x]);
}
Console.WriteLine('*');
}
Console.WriteLine(_line);
}
public IMemento CreateMemento()
{
var state = new string(_data);
var memento = new Memento(state);
return memento;
}
public void RestoreMemento(IMemento memento)
{
var state = memento.GetState();
_data = state.ToCharArray();
}
}
// Caretaker
public class MementoDemo
{
private readonly Stack<IMemento> _history = [];
private readonly Canvas _canvas = new();
public static void Execute() => new MementoDemo().Run();
private void Run()
{
Perform(() => Write(3, 2, "Hello"));
Perform(() => Write(3, 3, "There"));
Perform(() => Write(3, 4, "Friends"));
Undo();
Undo();
Perform(() => Write(3, 3, "World!"));
_canvas.Draw();
}
private void Undo()
{
if (_history.Count == 0) return;
var memento = _history.Pop();
_canvas.RestoreMemento(memento);
}
private void Perform(Action action)
{
var memento = _canvas.CreateMemento();
_history.Push(memento);
action();
}
private void Write(int x, int y, string text)
{
for (var i = 0; i < text.Length; ++i)
{
_canvas.Plot(x + i, y, text[i]);
}
}
}
// outputs:
// ************
// * *
// * Hello *
// * World! *
// * *
// ************
Notes
The Memento pattern fits particularly well in languages such as C++, where encapsulation can be maintained very tightly. For example, Memento can be declared as a friend of the Originator, allowing it controlled access to private state without exposing that state more broadly.
This also illustrates an important general point about design patterns: they are not rigid templates to be reproduced mechanically, but expressions of broader design principles. In practice, they often need to be adapted to the idioms, constraints, and capabilities of a particular programming language, system architecture, or codebase.
In implementation, Memento should usually remain a simple object whose purpose is only to capture state. Mementos are often best treated as immutable value objects. The Originator should provide operations for creating and restoring mementos, while the Caretaker should manage previously saved states without being given direct access to the internal details of either the originator or the stored state. Where undo and redo are required, the caretaker can often support them by maintaining separate stacks of prior and reverted states.
Summary
For more information, please see here.