Memento Design Pattern by Oladipo Timothy

The Memento pattern is best used whenever we want to keep track of changes to the state of an object. Most time in a chess game or perhaps in an application that undo-s. We want to be able to go the previous state of an object easily.

The easiest solution that comes to mind is keeping such changes in a variable.

Keep them where it is safe

THE PROBLEM

public class Editor{
   public string Content {get; set;}
   public string PrevContent {get; set;}

   public void StoreState(string content){
      PrevContent = content;
   }
}

Editor editor = new Editor();
editor.Content = "Hello";
editor.StoreState(editor.Content);

But looking at the approach, can you take a few seconds to get guess the defects in it?

What if I have multiple content state changes. I can only go one step back! Not cool for what we are trying to achieve.

THE SOLUTION

What next can we do? Use List or Arrays 🙂

Absolutely… We can easily have a list that stores the state in the Editor class that that defiles the SRP(Single Responsibility pattern) and it doesn’t make sense to store the state of an object in the object itself.

That means we need another class. A History class or sometimes called the Caretaker. it manages the state for us with a List of any type of Data structure.

public class History
    {
        private readonly List<EditorState> _stateManagement;

        public History()
        {
            _stateManagement = new List<EditorState>();
        }

        public void Push(EditorState state)
        {
           //add to the list
            _stateManagement.Add(state);
        }

        public EditorState Pop()
        {
            int lastIdx = -1;  

            //lets get the last state to delete from history
            lastIdx = GetLastIdx(_stateManagement);
            if (lastIdx >= 0)
                _stateManagement.Remove(_stateManagement[lastIdx]);

            //get current state to restore for Editor
            lastIdx = GetLastIdx(_stateManagement);
            if (lastIdx >= 0)
                return _stateManagement[lastIdx];

            //return an empty state because nothing is left in the history
            return new EditorState();
        }

        private int GetLastIdx(List<EditorState> stateManagement)
        {
            // tries to pick the last item of the list and prevent out of bounds
            if (stateManagement.Count > 0)
                return stateManagement.Count - 1;

            return -1;
        }

        public void PrintHistory()
        {
            //print all element in the history
            foreach (var item in _stateManagement)
            {
                Console.WriteLine(JsonConvert.SerializeObject(item));
            }
        }
    }

But before that, we need to be able to create/Restore a state and that would be independent of the Editor class. With that, our History class won’t have to interact directly with the class itself but with a state class.

That is where the Memento comes in or the Editor state class. It is just the copy of the editor properties that we want to TRACK.

public class EditorState{
     public string Title { get; set; }
     public string Content { get; set; }
     public int ContentLength { get; set; }
}

Then our editor would be;

public class Editor
    {
        public string Title { get; set; }
        public string Content { get; set; }
        public int ContentLength { get; set; }

        public EditorState CreateState()
        {
            return new EditorState()
            {
                Title = this.Title,
                Content = this.Content,
                ContentLength = this.ContentLength
            };
        }


        public void RestoreState(EditorState state)
        {
            Title = state.Title;
            Content = state.Content;
            ContentLength = state.ContentLength;
        }
    }

var editor = new Editor();
Here we declare our editor class

var history = new History();
This is the history class that stores our state in a list or stack!

editor.Content = "a";
history.Push(editor.CreateState());

Here it shows for every state change, we want to keep track of it with the history. The editor class creates a new EditorState and pushes it to the history class

editor.Content = "b";
history.Push(editor.CreateState());
editor.Content = "c";
history.Push(editor.CreateState());
editor.Title = "hello World";
history.Push(editor.CreateState());
editor.ContentLength = 50;
history.Push(editor.CreateState());

POP

Let talk pop!, just like stacks. FIFO –> first in, first out.

editor.RestoreState(history.Pop());

The RestoreState calls the pop that pushes the TOPMOST element off the history and assign the current peak into the editor class

editor.RestoreState(history.Pop());
editor.RestoreState(history.Pop());
editor.RestoreState(history.Pop());
editor.RestoreState(history.Pop());
editor.RestoreState(history.Pop());

history.PrintHistory();

This prints all the elements in the history stack.

Console.WriteLine(JsonConvert.SerializeObject(editor));

I could feel ambitious and change the History class to a generic type lol –> Which means it is compatible with any state management state for future use.

public class History<T> where T : new()
    {
        private readonly List<T> _stateManagement;

        public History()
        {
            _stateManagement = new List<T>();
        }

        public void Push(T state)
        {
            _stateManagement.Add(state);
        }

        public T Pop()
        {
            int lastIdx = -1;  

            lastIdx = GetLastIdx(_stateManagement);
            if (lastIdx >= 0)
                _stateManagement.Remove(_stateManagement[lastIdx]);

            //get current state to restore for Editor
            lastIdx = GetLastIdx(_stateManagement);
            if (lastIdx >= 0)
                return _stateManagement[lastIdx];

            return new T();
        }

        private int GetLastIdx(List<T> stateManagement)
        {
            if (stateManagement.Count > 0)
                return stateManagement.Count - 1;

            return -1;
        }

        public void PrintHistory()
        {
            foreach (var item in _stateManagement)
            {
                Console.WriteLine(JsonConvert.SerializeObject(item));
            }
        }
    }
This article was written by Oladipo Timothy… 

More on Devs ‘n’ Techies;