In this post I’ll explore the Memento Design Pattern which is a Behavioural pattern. If this is the first time you are coming to this site, you can also checkout my earlier posts related to Design Patterns And Enterprise Patterns. Gang of Four defines Memento pattern as a way to capture the internal state of an object without violating the encapsulation. It allows us to restore the state at a later point of time.
Problem statement
Assume we are building shopping cart software for a retailer. The user has an option to persist the shopping cart and amend it on subsequent visits to the website of the retailer. Until the user checks out the order and makes the payment, he or she is allowed to amend the order unlimited times. During modification user can also decide not to persist his changes in which case any unsaved changes should be reverted and the order state should be set to the last persisted state.
Shopping Cart without Memento
We start with a very basic ShoppingCart class which consists of different methods for manipulating the cart items collection.
public class ShoppingCart
{
private IList<CartItem> currentCartItems;
public ShoppingCart()
{
CartItems = new List<CartItem>
{
new CartItem { Id = 1, ProductName = "Lays Chips", Quantity = 2, UnitPrice = 5 },
new CartItem { Id = 2, ProductName = "Coca Cola", Quantity = 1, UnitPrice = 15 }
};
}
public int Id { get; set; }
public IList<CartItem> CartItems { get; set; }
public void AddItem(CartItem cartItem)
{
CartItems.Add(cartItem);
}
public void RemoveItem(CartItem cartItem)
{
CartItems.Remove(cartItem);
}
public void EditCart()
{
currentCartItems = new List<CartItem>(CartItems);
}
public void CancelEditing()
{
CartItems = new List<CartItem>(currentCartItems);
}
public void SaveCart()
{
// persist changes to database
}
}
In the above code snippet the constructor initializes the CartItems collection with two items. This kind of simulates the fetching of existing items from the persisted database. Assume that there is some service which takes care of populating these CartItem objects with the values from the persistent medium.
The ShoppingCart class exposes following methods to manipulate the contents of the cart.
- AddItem adds new CartItem to the items collection
- RemoveItem removes existing item from the collection
- EditCart method is used to change the state of cart from read only mode to update mode
- CancelEditing method is used to cancel the changes which are not yet saved into the database
- SaveCart is used to persist the changes to the database.
The workflow is as follows, user chooses to put the Cart into Edit mode by invoking the EditCart method. At this point a copy of the existing items are saved into a variable currentCartItems. The user can modify the cart items by adding or removing the items. The changes can be persisted by invoking SaveCart method or cancel edits by invoking the CancelEdit method.
ShoppingCart shoppingCart = new ShoppingCart();
Console.WriteLine("Print initial state");
PrintCartDetails(shoppingCart.CartItems);
shoppingCart.EditCart();
shoppingCart.AddItem(new CartItem { Id = 3, ProductName = "Pepsi", Quantity = 1, UnitPrice = 2 });
Console.WriteLine("Print after adding 1 cart item");
PrintCartDetails(shoppingCart.CartItems);
shoppingCart.CancelEditing();
Console.WriteLine("Print after cancelling edit");
PrintCartDetails(shoppingCart.CartItems);
In the above code snippet, we added a new item to the shopping cart and cancelled the change. Since persisting changes to database are outside the scope of this post, I’ll not touch upon that topic here.
The ShoppingCart class gets the job done as per the requirement. This would be sufficient in most cases. Do you see any flaw with this approach? Although not a major flaw but this method does pose a problem because the state is stored within the same class which can be restored later. Since the data is available to class methods, it might be possible for some other method to change the state unintentionally. How can we avoid such unintentional modification to the internal state of the object?
The Memento Design Pattern comes handy in the situation explained above. We can externalize the storage of CartItems instead of storing them in the currentCartItems variables. At the points where we wish to restore the collection, we restore it from the external source. By providing read-only access to the state of the external object we can avoid tempering with the intermediate state.
Refactoring towards Memento Pattern
We start off with defining a class which will store the data that we are interested in. This happens to be the cart items collection. We define the CartItemsMemento class as shown below
public class CartItemsMemento
{
private readonly IList<CartItem> _cartItems;
public CartItemsMemento(IList<CartItem> cartItems)
{
_cartItems = new List<CartItem>(cartItems);
}
public IList<CartItem> CartItems
{
get
{
return _cartItems;
}
}
}
The only responsibility for this class is to hold onto the state of an object which can be restored later. Next step is to get this data structure out of the ShoppingCart class. Here is the refactored ShoppingCart implementation.
public class ShoppingCart
{
public ShoppingCart()
{
CartItems = new List<CartItem>
{
new CartItem { Id = 1, ProductName = "Lays Chips", Quantity = 2, UnitPrice = 5 },
new CartItem { Id = 2, ProductName = "Coca Cola", Quantity = 1, UnitPrice = 15 }
};
}
public int Id { get; set; }
public IList<CartItem> CartItems { get; set; }
public void AddItem(CartItem cartItem)
{
CartItems.Add(cartItem);
}
public void RemoveItem(CartItem cartItem)
{
CartItems.Remove(cartItem);
}
public void EditCart()
{
// changes state from readonly to edit mode
}
public void CancelEditing()
{
// reverts back to readonly mode
}
public CartItemsMemento CreateMemento()
{
return new CartItemsMemento(CartItems);
}
public void RestoreCartItems(CartItemsMemento memento)
{
CartItems = new List<CartItem>(memento.CartItems);
}
public void SaveCart()
{
// persist changes to database
}
}
We have got rid of the currentCartItems private variable. Instead we have added two method CreateMemento and RestoreCartItems. CreateMemento returns a new instance of CartItemsMemento with the current shopping cart items. The restore method is used to restore the state stored inside the memento object.
By doing these changes we have the source of the object and the destination where it needs to reside temporarily. How do we glue them together. To bring the pieces together we have another intermediate class which acts as the caretaker of this data. I named it as CartItemsCareTaker.
public class CartItemsCareTaker
{
public CartItemsMemento CartItemsMemento { get; set; }
}
The only job of this class is to temporarily hold onto the internal state stored inside of the memento object. We are almost done with these changes. Here is the client code which makes use of these classes.
ShoppingCart shoppingCart = new ShoppingCart();
Console.WriteLine("Print initial state");
PrintCartDetails(shoppingCart.CartItems);
CartItemsCareTaker careTaker = new CartItemsCareTaker { CartItemsMemento = shoppingCart.CreateMemento() };
shoppingCart.AddItem(new CartItem { Id = 3, ProductName = "Pepsi", Quantity = 1, UnitPrice = 2 });
Console.WriteLine("Print after adding 1 cart item");
PrintCartDetails(shoppingCart.CartItems);
shoppingCart.RestoreCartItems(careTaker.CartItemsMemento);
Console.WriteLine("Print after cancelling edit");
PrintCartDetails(shoppingCart.CartItems);
Note that we invoke the shoppingCart.CreateMemento and shoppingCart.RestoreCartItems methods here.
Conclusion
Although using Memento pattern increases the number of classes in the solution, it increases the maintainability by splitting the responsibilities between the Originator (ShoppingCart), the Care taker (CartItemsCareTaker) and Memento (CartItemsMemento) classes. Each class has a unique responsibility. We achieve the objective of separating the internal state of an object using an encapsulated manner with the help of the care taker class. Anytime there is a need to backup and restore the internal state of object we should consider using the Memento pattern instead of managing the state within the same class.
The complete working solution is available for download Memento Design Pattern Demo.zip
Until Next time Happy Programming.
Further Reading
Based on the topics discussed in this post I would like to recommend following books.