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
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
Create a WPF application with a window as shown below
MainWindow.xaml
<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
namespace MementoPattern
{
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;
}
}
public EmployeeMemento SaveEmployee()
{
return new EmployeeMemento(this._firstName, this._lastName, this._id);
}
public void RestoreEmployee(EmployeeMemento empMemento)
{
this._firstName = empMemento.FirstName;
this._lastName = empMemento.LastName;
this._id = empMemento.Id;
}
}
}
EmployeeMemento.cs
namespace MementoPattern
{
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
using System.Collections.Generic;
namespace MementoPattern
{
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
- Creating and loading an employee.
Subscribe to the Window_Loaded event and in the handler create an employee.
_emp = new Employee("Praveen", "Raghuvanshi", 1);
- Saving the State:
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.
CareTaker.Instance.EmployeeMemento = _emp.SaveEmployee();
- Restoring the state:
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
.
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.
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
{
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);
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.
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.