Blog / Design Patterns
The Memento Design Pattern in C# and .Net
  • Jan 02, 2021
  • 56
  • 84

The next design pattern in the Behavioral Category is the Memento Design Pattern. Let's take a look at the definition of this design pattern and how to implement it in C# and .Net.

Note: Code can be downloaded at my Github.


Advertisement
 


1. What is the Memento Design Pattern?

The Memento Design Pattern is in Behavioral Category. This design pattern helps you to save and restore the previous state of an object without exposing the implementation details.

The memento pattern is a software design pattern that provides the ability to restore an object to its previous state. The memento pattern is implemented with three objects: the originator, a caretaker and a memento.

2. When to implement the Memento Design Pattern?

The Memento Design Pattern is implemented when:

  • you want to produce snapshots of the object’s state to be able to restore a previous state of the object.
  • direct access to the object’s fields/getters/setters violates its encapsulation.

3. How to implement the Memento Design Pattern?

The Memento Design Pattern is implemented as below:

Step 1: The Originator

The Originator holds some important state that may change over time. It also defines a method for saving the state inside a memento and another method for restoring the state from it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Originator
{
    // For the sake of simplicity, the originator's state is stored inside a
    // single variable.
    private string _state;
 
    public Originator(string state)
    {
        this._state = state;
        Console.WriteLine("Originator: My initial state is: " + state);
    }
 
    // The Originator's business logic may affect its internal state.
    // Therefore, the client should backup the state before launching
    // methods of the business logic via the save() method.
    public void DoSomething()
    {
        Console.WriteLine("Originator: I'm doing something important.");
        this._state = this.GenerateRandomString(30);
        Console.WriteLine($"Originator: and my state has changed to: {_state}");
    }
 
    private string GenerateRandomString(int length = 10)
    {
        string allowedSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        string result = string.Empty;
 
        while (length > 0)
        {
            result += allowedSymbols[new Random().Next(0, allowedSymbols.Length)];
 
            Thread.Sleep(12);
 
            length--;
        }
 
        return result;
    }
 
    // Saves the current state inside a memento.
    public IMemento Save()
    {
        return new ConcreteMemento(this._state);
    }
 
    // Restores the Originator's state from a memento object.
    public void Restore(IMemento memento)
    {
        if (!(memento is ConcreteMemento))
        {
            throw new Exception("Unknown memento class " + memento.ToString());
        }
 
        this._state = memento.GetState();
        Console.Write($"Originator: My state has changed to: {_state}");
    }
}

Step 2: The Memento

The Memento interface provides a way to retrieve the memento's metadata, such as creation date or name. However, it doesn't expose the Originator's state.

1
2
3
4
5
6
7
8
public interface IMemento
{
    string GetName();
 
    string GetState();
 
    DateTime GetDate();
}

The Concrete Memento contains the infrastructure for storing the Originator's state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ConcreteMemento : IMemento
{
    private string _state;
 
    private DateTime _date;
 
    public ConcreteMemento(string state)
    {
        this._state = state;
        this._date = DateTime.Now;
    }
 
    // The Originator uses this method when restoring its state.
    public string GetState()
    {
        return this._state;
    }
 
    // The rest of the methods are used by the Caretaker to display
    // metadata.
    public string GetName()
    {
        return $"{this._date} / ({this._state.Substring(0, 9)})...";
    }
 
    public DateTime GetDate()
    {
        return this._date;
    }
}

Step 3: The Caretaker

The Caretaker doesn't depend on the Concrete Memento class. Therefore, it doesn't have access to the originator's state, stored inside the memento. It works with all mementos via the base Memento interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Caretaker
{
    private List<IMemento> _mementos = new List<IMemento>();
 
    private Originator _originator = null;
 
    public Caretaker(Originator originator)
    {
        this._originator = originator;
    }
 
    public void Backup()
    {
        Console.WriteLine("\nCaretaker: Saving Originator's state...");
        this._mementos.Add(this._originator.Save());
    }
 
    public void Undo()
    {
        if (this._mementos.Count == 0)
        {
            return;
        }
 
        var memento = this._mementos.Last();
        this._mementos.Remove(memento);
 
        Console.WriteLine("Caretaker: Restoring state to: " + memento.GetName());
 
        try
        {
            this._originator.Restore(memento);
        }
        catch (Exception)
        {
            this.Undo();
        }
    }
 
    public void ShowHistory()
    {
        Console.WriteLine("Caretaker: Here's the list of mementos:");
 
        foreach (var memento in this._mementos)
        {
            Console.WriteLine(memento.GetName());
        }
    }
}

Done, you can implement the code on client side:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Program
{
    static void Main(string[] args)
    {
        // Client code.
        Originator originator = new Originator("Super-duper-super-puper-super.");
        Caretaker caretaker = new Caretaker(originator);
 
        caretaker.Backup();
        originator.DoSomething();
 
        caretaker.Backup();
        originator.DoSomething();
 
        caretaker.Backup();
        originator.DoSomething();
 
        Console.WriteLine();
        caretaker.ShowHistory();
 
        Console.WriteLine("\nClient: Now, let's rollback!\n");
        caretaker.Undo();
 
        Console.WriteLine("\n\nClient: Once more!\n");
        caretaker.Undo();
 
        Console.WriteLine();
    }
}

Output:


4. Conclusion

This design pattern is helpful if you are having a text application or at least you would like to let users to undo their action in your application. I hope this article is helpful. Please let me know your thoughts under the comment section below.


If you have a Website or a Web API developed by using .Net Core and looking for a way to publish your applications, this post will explain how to do it using GoDaddy Windows Hosting.Note: at this mome ...

Search text in Stored Procedure in SQL SELECT DISTINCT o.name AS Object_Name, o.type_desc FROM sys.sql_modules m INNER JOIN sys.objects o ON m.object_id = o ...

Using cherry-pick to select specific commits for your Pull Request.1. Create a new branch based on the target of the Pull Requestgit branch cherry-branch origin/master2. Switch to a new branchgit chec ...

After deployment Angular and API on IIS, it's working fine unless I refresh the page. Once refreshed, the web encountered 404 error. In this article, I will explain how to resolve this.Since Angular i ...

There are some benefits of keeping both UI and API parts in the same place for small projects. In this article, I will explain how I did to deploy Angular Web and ASP .Net Core API in the same folder ...

I got CORS error after publishing my API and Angular app to IIS even though CORS is enabled and the origins of the Angular app is added. Below is how I resolved this issue.Just simple, make sure you s ...

1. The Situation:Error Message:&nbsp;Pulse Secure Application failed to load Java. Please install correct JRE version.Description: This issue happens when I'm using a M1 Mac with a correct version of ...

Accelerated Mobile Pages (AMP)&nbsp;focuses on delivering static content from publishers as quickly as possible and possibly rewards early adopters with a boost in rank. Let's see how to implement it ...

Below is how to decrypt/convert a Hex string value into text using VB.Net:Decrypting Hex string value to string in VB.Net Function HexToString(ByVal hex As String) As String Dim text As New Sy ...