Recipe3.30.Rolling Back Object Changes


Recipe 3.30. Rolling Back Object Changes

problem

You have an object that allows its state to be changed. However, you do not want these changes to become permanent if other changes to the system cannot be made at the same time. In other words, you want to be able to roll back the changes if any of a group of related changes fails.

Solution

Use the memento design pattern to allow your object to save its original state in order to roll back changes.

The memento design pattern allows object state to be saved so that it can be restored in response to a specific situation. The memento pattern is very useful for implementing undo/redo or commit/rollback actions. This pattern usually has an originator objecta new or existing object that needs to have an undo/redo or commit/ rollback style behavior associated with it. This originator object's statethe values of its fieldswill be mirrored in a memento object, which is an object that can store the state of an originator object. Another object that usually exists in this type of pattern is the caretaker object. The caretaker is responsible for saving one or more memento objects, which can then be used later to restore the state of an originator object.

The SomeDataOriginator class used in this recipe contains data that must be changed only if other system changes occur. Its source code is shown in Example 3-22.

Example 3-22. An originator object

 using System; using System.Collections; using System.Collections.Generic; public class SomeDataOriginator {     public SomeDataOriginator() {}     public SomeDataOriginator(int state, string id, string clsName)     {         this.state = state;         this.id = id;         this.clsName = clsName;     }     private int state = 1;     private string id = "ID1001";     private string clsName = "SomeDataOriginator";     public string ClassName     {         get {return (clsName);}         set {clsName = value;}     }     public string ID     {         get {return (id);}         set {id = value;}     }     public void ChangeState(int newState)     {         state = newState;     }     public void Display()     {         Console.WriteLine("State: " + state);         Console.WriteLine("Id: " + id);         Console.WriteLine("clsName: " + clsName);     }     // Nested Memento class used to save outer class' state.     public class Memento     {         public Memento(SomeDataOriginator data)         {             this.state = data.state;             this.id = data.id;             this.clsName = data.clsName;             this.originator = data;         }         private SomeDataOriginator originator = null;         private int state = 1;         private string id = "ID1001";         private string clsName = "SomeDataOriginator";                  internal void Rollback()         {             originator.clsName = this.clsName;             originator.id = this.id;             originator.state = this.state;         }     } } 

The MementoCareTaker<T> is the caretaker object, which saves a single state that the originator object can roll back to. Its source code is:

 public class MementoCareTaker<T>     where T : SomeDataOriginator.Memento {     private T savedState = default(T);     public T Memento     {         get {return (savedState);}         set {savedState = value;}     } } 

MultiMementoCareTaker<T> is another caretaker object that can save multiple states to which the originator object can roll back. Its source code is:

 public class MultiMementoCareTaker<T>     where T : SomeDataOriginator.Memento {     private List<T> savedState = new List<T>();     public T this[int index]     {         get {return (savedState[index]);}         set {savedState[index] = value;}     }     public void Add(T memento)     {     savedState.Add(memento); } public int Count {     get {return (savedState.Count);} } } 

Discussion

This recipe makes use of two caretaker objects. The first, MementoCareTaker<T>, saves a single object state that can later be used to roll an object back. The second, MultiMementoCareTaker<T>, uses a List<T> object to save multiple object states, thereby allowing many levels of rollbacks to occur. You can also think of MultiMementoCareTaker<T> as storing multiple levels of the undo/redo state.

The originator class, SomeDataOriginator, has the state, id, and clsName fields to store information. One thing you have to add to the class, which will not affect how it behaves or how it is used, is a nested Memento class. This nested class is used to store the state of its outer class. You use a nested class so that it can access the private fields of the outer class. This allows the Memento object to get copies of all the needed fields of the originator object without having to add special logic to the originator allowing it to give this field information to the Memento object.

The Memento class contains only private fields that mirror the fields in the outer object that you want to store. Note that you do not have to store all fields of an outer type, just the ones that you want to roll back or undo. The Memento object also contains a constructor that accepts a SomeDataOriginator object. The constructor saves the pointer to this object as well as its current state. There is also a single method called Rollback. The Rollback method is central to restoring the state of the current SomeDataOriginator object. This method uses the originator pointer to this object to set the SomeDataOriginator object's fields back to the values contained in this instance of the Memento object.

The caretaker objects store any Memento objects created by the application. The application can then specify which Memento objects to use to roll back an object's state. Remember that each Memento object knows which originator object to roll back. Therefore, you need to tell the caretaker object only to use a Memento object to roll back an object, and the Memento object takes care of the rest.

There is a potential problem with the caretaker objects that is easily remedied. The problem is that the caretaker objects are not supposed to know anything about the Memento objects. The caretaker objects in this recipe see only one method, the Rollback method, that is specific to the Memento objects. So, for this recipe, this is not really a problem. However, if you decide to add more logic to the Memento class, you need a way to shield it from the caretaker. You do not want another developer to add code to the caretaker objects that may allow it to change the internal state of any Memento objects they contain.

To the caretaker objects, each Memento object should simply be an object that contains the Rollback method. To make the Memento objects appear this way to the caretaker objects, you can place an interface on the Memento class. This interface is defined as follows:

 public interface IMemento {     void Rollback( ); } 

The Memento class is then modified as follows (changes are highlighted):

 public class Memento : IMemento {     public void Rollback( )     {         originator.clsName = this.clsName;         originator.id = this.id;         originator.state = this.state;     }     // The rest of this class does not change } 

The caretaker classes are modified as follows (changes are highlighted):

 public class MementoCareTaker<T>     where T: IMemento {     private T savedState = default(T);     internal T Memento     {         get {return (savedState);}         set {savedState = value;}     } } public class MultiMementoCareTaker<T>     where T: IMemento {     private List<T> savedState = new List<T>();     public T this[int index]     {         get {return (savedState[index]);}         set {savedState[index] = value;}     }     public void Add(T memento)     {         savedState.Add(memento);     }     public int Count     {         get {return (savedState.Count);}     } } 

Implementing the IMemento interface serves two purposes. First, it prevents the caretaker classes from knowing anything about the internals of the Memento objects they contain. Second, it allows the caretaker objects to handle any type of Memento object, so long as it implements the IMemento interface.

The following code shows how the SomeDataOriginator, Memento, and caretaker objects are used. It uses the MementoCareTaker<T> object to store a single state of the SomeDataOriginator object and then rolls the changes back after the SomeDataOriginator object is modified:

 // Create an originator and default its internal state. SomeDataOriginator data = new SomeDataOriginator(); Console.WriteLine("ORIGINAL"); data.Display(); // Create a caretaker object. MementoCareTaker<SomeDataOriginator.Memento> objState =                new MementoCareTaker<SomeDataOriginator.Memento>(); // Add a memento of the original originator object to the caretaker. objState.Memento = new SomeDataOriginator.Memento(data); // Change the originator's internal state. data.ChangeState(67); data.ID = "foo"; data.ClassName = "bar"; Console.WriteLine("NEW"); data.Display(); // Roll back the changes of the originator to its original state objState.Memento.Rollback(); Console.WriteLine("ROLLEDBACK"); data.Display(); 

This code outputs the following:

 ORIGINAL State: 1 Id: ID1001 ClsName: SomeDataOriginator NEW State: 67 Id: foo ClsName: bar ROLLEDBACK State: 1 Id: ID1001 ClsName: SomeDataOriginator 

The use of the MultiMementoCareTaker<T> object is very similar to the MementoCareTaker object, as the following code shows:

 SomeDataOriginator Data = new SomeDataOriginator(); Console.WriteLine("ORIGINAL"); Data.Display(); MultiMementoCareTaker<SomeDataOriginator.Memento> MultiObjState = new MultiMementoCareTaker<SomeDataOriginator.Memento>(); MultiObjState.Add(new SomeDataOriginator.Memento(Data)); Data.ChangeState(67); Data.ID = "foo"; Data.ClassName = "bar"; Console.WriteLine("NEW"); Data.Display(); MultiObjState.Add(new SomeDataOriginator.Memento(Data)); Data.ChangeState(671); Data.ID = "foo1"; Data.ClassName = "bar1"; Console.WriteLine("NEW1"); Data.Display(); MultiObjState.Add(new SomeDataOriginator.Memento(Data)); Data.ChangeState(672); Data.ID = "foo2"; Data.ClassName = "bar2"; Console.WriteLine("NEW2"); Data.Display(); MultiObjState.Add(new SomeDataOriginator.Memento(Data)); Data.ChangeState(673); Data.ID = "foo3"; Data.ClassName = "bar3"; Console.WriteLine("NEW3"); Data.Display(); for (int Index = (MultiObjState.Count - 1); Index >= 0; Index--) {     Console.WriteLine("\r\nROLLBACK(" + Index + ")");     MultiObjState[Index].Rollback();     Data.Display(); } 

This code outputs the following:

 ORIGINAL State: 1 Id: ID1001 ClsName: SomeDataOriginator NEW State: 67 Id: foo ClsName: bar NEW1 State: 671 Id: foo1 ClsName: bar1 NEW2 State: 672 Id: foo2 ClsName: bar2 NEW3 State: 673 Id: foo3 ClsName: bar3 ROLLBACK(3) State: 672 Id: foo2 ClsName: bar2 ROLLBACK(2) State: 671 Id: foo1 ClsName: bar1 ROLLBACK(1) State: 67 Id: foo ClsName: bar ROLLBACK(0) State: 1 Id: ID1001 ClsName: SomeDataOriginator 

This code creates a SomeDataOriginator object and changes its state several times. At every state change, a new Memento object is created to save the SomeDataOriginator object's state at that point in time. At the end of this code, a for loop iterates over each Memento object stored in the MultiMementoCareTaker<SomeDataOriginator. Memento> object, from the most recent to the earliest. On each iteration of this loop, the Memento object is used to restore the state of the SomeDataOriginator object.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net