Memento Pattern in C#
Kostas Kalafatis
Posted on October 7, 2022
The Memento is a behavioural design pattern that lets us save and restore an object's previous states without revealing its implementation details.
The Memento pattern seeks to capture and externalize an object's state so that the object can be restored to this state later.
The purpose of this pattern is to separate the current state of the object from a previous state so that if something happens to the current state (it gets corrupted, it gets lost, it tries to secede from the Empire), the object's state can be restored from its Memento (whether via a civil war or other, less spectacular methods).
You can find the example code of this post, on GitHub
Conceptualizing the Problem
Let's say we're creating a text editor application. In addition to simple text editing, our editor can format text, and insert inline images.
At some point, we decided to let users undo any operations carried out on the text. This feature has become so commonplace that people expect every application to have it. For the implementation, we chose to take the direct approach. Before performing any operation, the application records the state of all objects and saves them in some storage. Later, when a user decides to revert an action, the application fetches the latest snapshot from the history and uses it to restore the state of all objects.
Let's think about these state snapshots. How exactly would we produce one? We'd probably need to iterate all the fields in an object and copy their values into storage. However, this would only work if the class had relaxed access restrictions to its contents. Unfortunately, most real classes won't let others peek inside their state that easily, hiding all essential data in private fields.
Ignore that problem for now and let's assume that our objects behave quite liberally: preferring open relations and keeping their state public. While this approach would solve the immediate problem and let us produce snapshots of the object's states at will, it still has some serious issues. In the future, if we decide to refactor some of the fields. Sounds easy, but this would also require changing the classes responsible for copying the state of the affected objects.
And there's more. Let's consider the actual snapshots of the editor's states. What data will it save? At a bare minimum, it must contain the text, cursor coordinates, current scroll position and styling. To make a snapshot, we'd need to collect these values and put them into a container.
Most likely, we're going to store lots of these container objects inside some list that would represent the editor's history. Therefore the containers would probably end up being objects of one class. The class would have almost no methods, just fields reflecting the editor's state. To allow other objects to interact with the snapshot, we'd probably need to make all of its fields public. That would expose all the editor's states, private or not. Moreover, other classes would become dependent on every little change to the snapshot class, which would otherwise happen within private fields and methods without affecting outer classes.
We either have to expose all internal details of classes, making them too fragile or restrict access to their state, making it impossible to produce snapshots.
All problems that we've just experienced are a product of broken encapsulation. Some objects try to do more than they're supposed to. To collect the data required to perform some action, they invade the privacy of other objects instead of letting them act.
The Memento pattern delegates creating the state snapshots to the actual owner of that state, the originator object. Hence, instead of other objects trying to copy the editor's state from the "outside", the editor class itself can make the snapshot since it has full access to its state.
The pattern suggests storing the copy of the object's state in a memento object. The contents of the memento aren't accessible to any other object except the one that produced it. Other classes must communicate with mementoes using a thin interface which may allow fetching the snapshot's metadata, such as creation time, the performed action, etc., but not the original object's state contained in the snapshot.
Such a restrictive policy allows us to store mementoes inside other objects, usually called caretakers. Yes, the terminology of this pattern is a bit gloomy. Since the caretaker works with the memento only via a limited interface, it's not able to tamper with the state stored inside the memento. At the same time, the originator has access to all fields inside the memento, allowing it to restore its previous state at will.
Structuring the Solution
There are three implementations of the Memento pattern.
Implementation using nested classes
The classic implementation of the pattern relies on nested classes. In this implementation there are three participants:
- Originator: The Originator class can produce snapshots of its state, as well as restore its state from snapshots when needed.
- Memento: The Memento is a value object that acts as a snapshot of the originator's state. It's a common practice to make the memento immutable and pass it the data only once, via the constructor.
- Caretaker: The Caretaker knows not only “when” and “why” to capture the originator’s state, but also when the state should be restored. A caretaker can keep track of the originator’s history by storing a stack of mementoes. When the originator has to travel back in history, the caretaker fetches the topmost memento from the stack and passes it to the originator’s restoration method.
In this implementation, the memento class is nested inside the originator. This lets the originator access the fields and methods of the memento, even though they’re declared private. On the other hand, the caretaker has very limited access to the memento’s fields and methods, which lets it store mementoes in a stack but does not tamper with their state.
Implementation using intermediate interfaces
This alternative implementation is suitable when in cases we can't use nested classes.
In the absence of nested classes, we can restrict access to the memento's fields by establishing a convention that caretakers can work with a memento only through an explicitly declared intermediary interface, which would only declare methods related to the memento's metadata.
On the other hand, originators can work with a memento object directly, accessing fields and methods declared in the memento class. The downside of this approach is that we need to declare all members of the memento public.
Implementation using strict encapsulation
This implementation is useful when we don't want to leave even the slightest chance of other classes accessing the state of the originator through the memento.
This implementation allows having multiple types of originators and mementoes. Each originator works with a corresponding memento class. Neither originators nor mementoes expose their state to anyone.
Caretakers are now explicitly restricted from changing the state stored in mementoes. Moreover, the caretaker class becomes independent from the originator because the restoration method is now defined in the memento class.
Each memento becomes linked to the originator that produced it. The originator passes itself to the memento’s constructor, along with the values of its state. Thanks to the close relationship between these classes, a memento can restore the state of its originator, given that the latter has defined the appropriate setters.
To demonstrate how the memento pattern works, we are going to create an ordering system for a high-end restaurant.
Let's imagine a system in which a restaurant needs to record information about the suppliers that bring them their ingredients. For example, a high-end restaurant might order directly from a local farm, and the restaurant needs to keep track of which ingredients come from which supplier.
In our system, we need to keep track of how much information we enter about a particular supplier and be able to restore that information to a previous state if we, say, accidentally enter the wrong address. We can do this using the Memento pattern.
First, let's create our Originator participant, the class FoodSupplier
, which will create and use Mementoes:
using Memento.Memento;
namespace Memento.Originator
{
/// <summary>
/// The Originator class, which is the class for which we want to save
/// Mementos for its state.
/// </summary>
public class FoodSupplier
{
private string? name;
private string? phoneNumber;
private string? address;
public string Name
{
get => name;
set
{
name = value;
Console.WriteLine($"Proprietor: {name}");
}
}
public string PhoneNumber
{
get => phoneNumber;
set
{
phoneNumber = value;
Console.WriteLine($"Phone Number: {phoneNumber}");
}
}
public string Address
{
get => address;
set
{
address = value;
Console.WriteLine($"Address: {address}");
}
}
public FoodSupplierMemento SaveState()
{
Console.WriteLine("\nSaving current state\n");
return new FoodSupplierMemento(name, phoneNumber, address);
}
public void RestoreState(FoodSupplierMemento memento)
{
Console.WriteLine("\nRestoring previous state\n");
Name = memento.Name;
PhoneNumber = memento.PhoneNumber;
Address = memento.Address;
}
}
}
We also need a Memento participant, which is the FoodSupplierMemento
class used by FoodSupplier
:
namespace Memento.Memento
{
/// <summary>
/// The Memento class
/// </summary>
public class FoodSupplierMemento
{
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Address { get; set; }
public FoodSupplierMemento(string name, string phoneNumber, string address)
{
Name = name;
PhoneNumber = phoneNumber;
Address = address;
}
}
}
Finally, we need our Caretaker, which stores the Mementoes but never inspects or modifies them. We will name this class SupplierMemory
:
using Memento.Memento;
namespace Memento.Caretaker
{
/// <summary>
/// The Caretaker class.
/// This class never examines the contents of any Memento and is
/// responsible for keeping that memento.
/// </summary>
public class SupplierMemory
{
private FoodSupplierMemento memento;
public FoodSupplierMemento Memento
{
set { memento = value; }
get { return memento; }
}
}
}
Now, in our Main()
method, we can simulate adding a new Supplier but accidentally adding the wrong address, then using the Memento to restore the old data:
using Memento.Caretaker;
using Memento.Originator;
FoodSupplier supplier = new FoodSupplier
{
Name = Faker.Name.FullName(),
PhoneNumber = Faker.Phone.Number(),
Address = Faker.Address.StreetAddress()
};
SupplierMemory memory = new SupplierMemory();
memory.Memento = supplier.SaveState();
supplier.Address = Faker.Address.StreetAddress();
supplier.RestoreState(memory.Memento);
And the output of the demo will be:
Pros and Cons of Memento Pattern
✔ We can produce snapshots of the object’s state without violating its encapsulation. | ❌ The app might consume lots of RAM if clients create mementoes too often. |
✔ We can simplify the originator’s code by letting the caretaker maintain the history of the originator’s state. | ❌ Caretakers should track the originator’s lifecycle to be able to destroy obsolete mementoes. |
Relations with Other Patterns
- We can use the Command and Memento together when implementing an undo functionality. In this case, commands are responsible for performing various operations over a target, while mementoes save the state of that object just before a command gets executed.
- We can use the Memento pattern along with Iterator to capture the current iteration state and roll it back if necessary.
- Sometimes Prototypes can be a simpler alternative to Memento. This works if the object, the state of which we want to store in history, is pretty straightforward and doesn't have links to external resources, or the links are easy to re-establish.
Final Thoughts
In this article, we have discussed what is the Memento pattern, when to use it and what are the pros and cons of using this design pattern. We then examined different implementations, and how the Memento pattern relates to other classic design patterns.
It's worth noting that the Memento pattern, along with the rest of the design patterns presented by the Gang of Four, is not a panacea or a be-all-end-all solution when designing an application. Once again it's up to the engineers to consider when to use a specific pattern. After all these patterns are useful when used as a precision tool, not a sledgehammer.
Posted on October 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.