DEV Community

Cover image for Memento Pattern
Nicolas
Nicolas

Posted on • Updated on

Memento Pattern

Have you ever wondered how you can overcome the undo problem in your applications. Today I want us to talk about one of the most common ways to resolve this this programming problem.

Lets get started, so imagine we have a client and our Client has given us a task to create a text editor for the his/her team.We have been told that that the text editor should allow the use to undo the changes they do as they use the text editor.

here are some of the requirements for the text editor:

  • Users Should be able to change text font

  • Users should be able to change the text size

  • As users add text they should be able to undo text changes,font changes and text size changes.

Now as a user these are just basic requirements, if you are a developer who has never came across a similar problem,how will you solve this?

Today I will introduce you to a common pattern for solving this problem. The name of the pattern we are going to talk about today is the Memento Pattern, The memento pattern is a software design pattern that provides the ability to restore objects to their previous state. The memento pattern is implemented with three objects : the originator, a caretaker and a memento. The originator is some object that has an internal state (in our example this will be the Text Editor),The memento (in our case the Restore Point) object is special object in that it carries the state of the originator and is not suppose to be modified by the the cartaker (Think of it as backup Disk of a text editor state,when the state of the text editor is modifed a backup disk with the text editor state is created).

The caretaker (in our case a view model) is an object that interacts or does changes to the originator and it will also be capable of requesting undo or revert actions. When our user types characters the caretaker first asks the text editor (originator) for a new memento object with the current text editors state, that will be pushed to a stack of mementos (restore points) , then our care taker sets the newly typed character into the text editor state ,this same process continues as the user types in more characters. To roll back to the state before the operation, the caretaker returns the last memento object to pushed into the stack to the originator(text editor). although this is simple straight and straight forward ,it is a very powerful functionality although it has drowbacks as we keep on storeing states objects in memory,this will take up more memory the more the user types, the solution would to serialise the states into a file, but by doing so you should keep in mind that IO operations to files are slower than using ram memory, so all these decisions will be based on what you want to do and how important it is for you to preseverse state changes.

lets get into the code. We are going to be using C#, (Remember this is not language specific ,this can be implemented in almost any general purpose programming language i can think of).

First lets create our console application with the following commands:

  • lets create a solution file

dotnet new sln -n NotIt -o ./NoteIt

  • Lets create our Console application

dotnet new console -n Noteit.Cli -o ./NoteIt/src/Cli/

  • Lets Add it to the solution

dotnet sln add ./NoteIt/src/Cli/NoteIt.Cli.csproj

(Note ) Step 1 and 3 are really necessary but as good practice for organising .Net projects, they should be grouped in solution folder and file.

Next navigate to the root folder of our NoteIt application and open visual studio code or any Text Editor you prefer, visual studio code is what I prefer.

Today we are just going to focus on the Cli project so we are going write everything in the cli project.

Firstly we need a Text Editor class :

namespace NoteIt.Cli
{
    public struct TextEditor
    {
        public TextEditor(int fontSize, string fontFamily)
        {
            FontSize = fontSize;
            FontFamily = fontFamily;
        }
        public string Content { get; set; } = string.Empty;
        public int FontSize { get; set; } = 12 ;
        public string FontFamily { get; set; } = string.Empty;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have an originator we need a memento, our memento should pass in a textEditor in the constructor.Once ourr state has been presevered No one is supposed to be able to modify this backup point, to make sure this does not happen we dnt create a setter for our state.

namespace NoteIt.Cli
{
    /// <summary>
    /// RestorePoint is a memento class for our TextEditor
    /// </summary>
    public class RestorePoint
    {
        public TextEditor State { get; }
        public RestorePoint(TextEditor editorState)
        {
            State = new TextEditor
            {
                Title = editorState.Title,
                Content = editorState.Content
            };
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Now that we have a Memento lets update out Editor to look like this :

namespace NoteIt.Cli.Editor
{

    public struct TextEditor
    {
        public TextEditor(int fontSize, string fontFamily)
        {
            FontSize = fontSize;
            FontFamily = fontFamily;
        }

        public string Content { get; set; } = string.Empty;
        public int FontSize { get; set; } = 12;
        public string FontFamily { get; set; } = string.Empty;

        public void Restore(RestorePoint restorePoint)
        {
            this = restorePoint.State;
        }
        public RestorePoint CreateRestorePoint()
        {
            return new RestorePoint(this);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Our TextEditor is now able to create a restore point and its also able to Restore its self to a given restore point.

We almost done now we just need to create our Caretaker

namespace NoteIt.Cli.Editor
{
    //Caretaker
    public class EditorViewModel
    {
        private Stack<RestorePoint> RestorePoints { get; set; } = new();
        private TextEditor textEditor = new(fontSize: 12, fontFamily: "Ubuntu mono");

        private void CreateRestorePoint()
        {
            RestorePoints.Push(textEditor.CreateRestorePoint());
        }

        public string Content
        {
            get => textEditor.Content;
            set
            {
                CreateRestorePoint();
                textEditor.Content = value;
            }
        }

        public string FontFamily
        {
            get => textEditor.FontFamily;
            set
            {
                CreateRestorePoint();
                textEditor.FontFamily = value;
            }
        }

        public int FontSize
        {
            get => textEditor.FontSize; set
            {
                CreateRestorePoint();
                textEditor.FontSize = value;
            }
        }
        public void Undo()
        {
            //Check if the is a restore point then restore to it.
            if (RestorePoints.Count > 1)
            {
                textEditor.Restore(RestorePoints.Pop());
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it , now we can create simulation of a user interacting with our Text editor.On the Program.cs file add the following code :

using NoteIt.Cli.Editor;
using static System.Console;

EditorViewModel viewModel = new();
string? textUserWillType = "Hello";


static void SimulateUserPause() => Thread.Sleep(500);

// simulator user interaction 
for (int x = 0; x < textUserWillType.Length; x++)
{
    SimulateUserPause();
    //add newly typed character to editor
    viewModel.Content = textUserWillType[..x];
    //display new content to screen
    WriteLine(viewModel.Content);
}


SimulateUserPause();
//Change Font Size  
viewModel.FontSize = 18;
WriteLine($"Font Size : {viewModel.FontSize}");
SimulateUserPause();
viewModel.FontSize = 35;
WriteLine($"Font Size : {viewModel.FontSize}");
//Undo two previous changes
viewModel.Undo();
SimulateUserPause();
WriteLine($"Font Size : {viewModel.FontSize}");
viewModel.Undo();
WriteLine($"Font Size : {viewModel.FontSize}");

//Change Font 
SimulateUserPause();
viewModel.FontFamily = "Lato";
WriteLine($"Font Family changed to {viewModel.FontFamily}");
//undo font change
viewModel.Undo();
WriteLine($"Font Family change undone, current font : {viewModel.FontFamily}");

//simulate Undo clicked 3 times
for (int i = 0; i < 3; i++)
{
    viewModel.Undo();
    WriteLine(viewModel.Content);
}

Enter fullscreen mode Exit fullscreen mode

That's it we have learned how to implement undo Memento pattern, Now the Challenge I have for you is to take this even further by ,thinking and implementing Redo ,then also persist your changes to file storage.

The source code for this article can be found at https://github.com/hnicolus/NoteIt-memento-Pattern-Demo

Discussion (0)