Click here to Skip to main content
15,887,822 members
Articles / Programming Languages / C#
Article

Practical approach to Memento design pattern

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
28 Dec 2012CPOL4 min read 32.8K   354   11  
This article explains the implementation of undo/redo using a Memento Pattern.

Introduction  

Many times we face a situation where we need to store the state of an object. One of the best examples is rollback operation where we need to restore the application to some previous state. This can be achieved by creating a copy of the object at certain point of time while working on the actual object. The actual object stores the list of own objects as a state. The object can be restored to some previous state through the list of objects. This approach is memory intensive as we keep a copy of the object at the defined states. The other drawback is violation of encapsulation in order to store the state of an object as we have to expose the internal state of an object such as member variables. Also, if we are interested only in some part of an object to be stored, we store the whole object, which is not required.

Background

Memento pattern is one the GOF design pattern that comes as a rescue to store the state of an object. Memento means a reminder of past events or a souvenir. In Memento pattern, we do not create a copy of an object. We create Mementos that hold the state of an object and it might include full object or elements of an object that needs to be stored.

Wikipedia definition of Memento Pattern

The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).

Ingredients of a Memento Pattern

  • Originator: It is the one whose state needs to be saved and creates the Memento object.
  • Memento: It holds the internal state of an Originator.
  • Caretaker: It is responsible for keeping the memento.

Class Diagram

Image 1

In order to save the state of an Originator, Caretaker asks the originator to create and pass the memento. The originator saves the internal state and passes the memento to the CareTaker. Caretaker maintains the memento. Whenever, Originator needs to be restored to a previous state, CareTaker calls the SetMemento of Originator and passes the saved memento. The originator accepts the memento and restores the originator to previous state.

Example:

Let’s take an example of an Employee whose state needs to be stored, and restored to a previous state.

Class Diagram

Image 2

Create a WPF application with a window as shown below

Image 3

MainWindow.xaml
XML
<Window x:Class="MementoPattern.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="309" Width="359" Loaded="Window_Loaded">
    <Grid Height="278">
        <Label Content="Employee Information" FontStyle="Italic" Height="28" 
             HorizontalAlignment="Left" Margin="57,33,0,0" 
             Name="labelEmployeeInfo" VerticalAlignment="Top" Width="129" />
        <Label Content="First Name" Height="28" HorizontalAlignment="Left" 
             Margin="57,67,0,0" Name="labelFirstName" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="149,71,0,0" 
             Name="textBoxFirstName" VerticalAlignment="Top" Width="120" />
        <Label Content="LastName" Height="28" HorizontalAlignment="Left" 
             Margin="57,110,0,0" Name="labelLastName" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="149,112,0,0" 
             Name="textBoxLastName" VerticalAlignment="Top" Width="120" />
        <Label Content="Id" Height="28" HorizontalAlignment="Left" 
             Margin="57,150,0,0" Name="labelId" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="149,153,0,0" 
             Name="textBoxId" VerticalAlignment="Top" Width="120" />
        <Button Content="Save" Height="23" HorizontalAlignment="Left" Margin="79,204,0,0" 
             Name="buttonSave" VerticalAlignment="Top" Width="75" Click="buttonSave_Click" />
        <Button Content="Cancel" Height="23" HorizontalAlignment="Left" Margin="175,206,0,0" 
             Name="buttonCancel" VerticalAlignment="Top" Width="75" Click="buttonCancel_Click" />
        <Button Content="Undo" Height="23" HorizontalAlignment="Left" Margin="79,245,0,0" 
             Name="buttonUndo" VerticalAlignment="Top" Width="75" Click="buttonUndo_Click" />
        <Button Content="Redo" Height="23" HorizontalAlignment="Left" Margin="175,247,0,0" 
             Name="buttonRedo" VerticalAlignment="Top" Width="75" Click="buttonRedo_Click" />
    </Grid>
</Window>

Ingredients of Memento Pattern

  • Employee: This represents the originator whose state needs to be stored.
  • EmployeeMemento: This class holds the state of the employee object. Currently, we are storing the whole object (FirstName, LastName, and Id). If required, we can store only the required things such as FirstName or LastName.
  • CareTaker class: It holds the state of an employee through an EmployeeMemento.

Functionality

  • Save state: Employee.SaveEmployee(): It creates the EmployeeMemento with the current state of the object and returns it.
  • Restore state: Employee.RestoreEmployee(): It restore the employee to the previous state by passing the memento which holds the previous state.
Employee.cs
C#
namespace MementoPattern
{
    /// <summary>
    /// Represents an Employee
    /// </summary>
    public class Employee
    {
        private string _firstName;
        private string _lastName;
        private uint _id;

        public Employee(string firstName, string lastName, uint id)
        {
            _firstName = firstName;
            _lastName = lastName;
            _id = id;
        }

        public string FirstName
        {
            get
            {
                return _firstName;
            }
            set
            {
                _firstName = value;
            }
        }

        public string LastName
        {
            get
            {
                return _lastName;
            }
            set
            {
                _lastName = value;
            }
        }

        public uint Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
            }
        }

        /// <summary>
        /// Save an employee to a memento
        /// </summary>
        /// <returns>employee memento</returns>
        public EmployeeMemento SaveEmployee()
        {
            return new EmployeeMemento(this._firstName, this._lastName, this._id);
        }

        /// <summary>
        /// Restore the employee to a previous state held by a memento
        /// </summary>
        /// <param name="empMemento">previous state memento</param>
        public void RestoreEmployee(EmployeeMemento empMemento)
        {
            this._firstName = empMemento.FirstName;
            this._lastName = empMemento.LastName;
            this._id = empMemento.Id;
        }
    }
}
EmployeeMemento.cs
C#
namespace MementoPattern
{
    /// <summary>
    /// Memento to get/set the state of an Employee
    /// </summary>
    public class EmployeeMemento
    {
        private string _firstName;
        private string _lastName;
        private uint _id;

        public EmployeeMemento(Employee emp)
        {
            _firstName = emp.FirstName;
            _lastName = emp.LastName;
            _id = emp.Id;
        }

        public EmployeeMemento(string firstName, string lastName, uint id)
        {
            _firstName = firstName;
            _lastName = lastName;
            _id = id;
        }

        public string FirstName
        {
            get
            {
                return _firstName;
            }
            set
            {
                _firstName = value;
            }
        }

        public string LastName
        {
            get
            {
                return _lastName;
            }
            set
            {
                _lastName = value;
            }
        }

        public uint Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
            }
        }
    }
}
CareTaker.cs
C#
using System.Collections.Generic;

namespace MementoPattern
{
    /// <summary>
    /// Caretaker to hold the undo/redo.
    /// </summary>
    public class CareTaker
    {
        private static CareTaker _instance;
        private EmployeeMemento _empMemento;
        
        private CareTaker()
        {
        }

        public static CareTaker Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new CareTaker();
                }
                return _instance;
            }
        }

        public EmployeeMemento EmployeeMemento
        {
            get
            {
                return _empMemento;
            }
            set
            {
                _empMemento = value;
            }
        }
    }
}

Now, let’s plug the things in the MainWindow.

MainWindow.xaml.cs
  1. Creating and loading an employee.
  2. Subscribe to the Window_Loaded event and in the handler create an employee.

    C#
    _emp = new Employee("Praveen", "Raghuvanshi", 1);  
  3. Saving the State:
  4. Once the user edits the values in the textbox and press Save button, call the employee SaveEmployee method which will create a memento and store the state of the employee in it.

    Add the memento to the CareTaker.

    C#
    CareTaker.Instance.EmployeeMemento = _emp.SaveEmployee();
  5. Restoring the state:
  6. Before restoring the state, we will store the current state in order to bring the object to the current state once object has been restored to previous state.

    The object is restored to previous state using the state in the memento. Current state is saved in the CareTaker.

    C#
    var empMemento = _emp.SaveEmployee();
    _emp.RestoreEmployee(CareTaker.Instance.EmployeeMemento);
    CareTaker.Instance.EmployeeMemento = empMemento;

Please refer below for the full source code of MainWindow.xaml.cs for more details.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MementoPattern
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Employee _emp;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            _emp = new Employee("Praveen", "Raghuvanshi", 1);
            this.buttonUndo.IsEnabled = this.buttonRedo.IsEnabled = false;
            Update();
        }
        
        private void buttonSave_Click(object sender, RoutedEventArgs e)
        {
            Save();
        }

        private void buttonCancel_Click(object sender, RoutedEventArgs e)
        {
            Revert();
        }

        private void buttonUndo_Click(object sender, RoutedEventArgs e)
        {
            SaveAndUpdateState();
            EnableUndo(false);
        }

        private void buttonRedo_Click(object sender, RoutedEventArgs e)
        {
            SaveAndUpdateState();
            EnableUndo(true);
        }

        private void Update()
        {
            textBoxFirstName.Text = _emp.FirstName;
            textBoxLastName.Text = _emp.LastName;
            textBoxId.Text = _emp.Id.ToString();
        }

        private void Save()
        {
            CareTaker.Instance.EmployeeMemento = _emp.SaveEmployee();

            _emp.FirstName = textBoxFirstName.Text;
            _emp.LastName = textBoxLastName.Text;
            _emp.Id = Convert.ToUInt16(textBoxId.Text);
            
            // Reset the undo/redo button
            EnableUndo(true);
        }

        private void Revert()
        {
            Update();
        }

        private void SaveAndUpdateState()
        {
            var empMemento = _emp.SaveEmployee();
            _emp.RestoreEmployee(CareTaker.Instance.EmployeeMemento);
            Update();
            CareTaker.Instance.EmployeeMemento = empMemento;
        }

        private void EnableUndo(bool isEnableUndo)
        {
            this.buttonUndo.IsEnabled = isEnableUndo;
            this.buttonRedo.IsEnabled = !isEnableUndo;
        }
    }
}

Launch the application and change the values in the textboxes. Press Save and Undo, observe that the previous values have been stored. Now, press Redo and observe that the last edited values is restored.

The states are saved in the CareTaker and Employee class does not store it. It remains lightweight.

Conclusion

Memento pattern is a powerful design pattern to store the state of an object. One other usage is the implementation of 'Ok' and 'cancel' dialog where we store the state of the object on load of dialog and work on the main object. If user presses 'Cancel', we restore to the initial state.

References 

I hope you enjoyed reading the article, and I look forward for any comments and suggestions in order to make it better.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Harman International
India India
I am a Microsoft .Net developer having experience in developing enterprise applications using technologies such as WPF, Winform. Always look towards making the life simple by developing GUI based applications.

Comments and Discussions

 
-- There are no messages in this forum --