DEV Community

Cover image for Memento Pattern in C#
Kostas Kalafatis
Kostas Kalafatis

Posted on • Originally published at dfordebugging.wordpress.com

Memento Pattern in C#

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.

Before executing an operation, the app saves a snapshot of the objects’ state, which can later be used to restore objects to their previous state.

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.

The originator has full access to the memento, whereas the caretaker can only access the metadata.

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 based on nested classes Diagram

Implementation using intermediate interfaces

This alternative implementation is suitable when in cases we can't use nested classes.

Implementation using intermediate interfaces Diagrams

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.

Implementation using strict encapsulation diagram

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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; }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

And the output of the demo will be:

Memento Demo Output

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.

Latest comments (2)

Collapse
 
venkatesh_chn profile image
Venkatesh

Will making the foodsupplier memento into a record be better rather than that being class that will make it immutable

Collapse
 
kalkwst profile image
Kostas Kalafatis

Actually yes, this is a great opportunity to use a record instead of class. But old habits die hard 😂😂😂

In most cases it is actually better to use a record as the memento, and right now I am struggling to find a use case where a class would be preferable, without either invalidating the pattern's purpose or violating the Single Responsibility Principle.

Thanks for pointing out!