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.
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.