Chapter 26. The Memento Pattern

In this chapter, we discuss how to use the Memento pattern to save data about an object so you can restore it later. For example, you might like to save the color, size, pattern, or shape of objects in a drafting or painting program. Ideally, it should be possible to save and restore this state without making each object take care of this task and without violating encapsulation. This is the purpose of the Memento pattern.

Motivation

Objects normally shouldn’t expose much of their internal state using public methods, but you would still like to be able to save the entire state of an object because you might need to restore it later. In some cases, you could obtain enough information from the public interfaces (such as the drawing position of graphical objects) to save and restore that data. In other cases, the color, shading, angle, and connection relationships to other graphical objects need to be saved, and this information is not readily available. This sort of information saving and restoration is common in systems that need to support Undo commands.

If all of the information describing an object is available in public variables, it is not that difficult to save them in some external store. However, making these data public makes the entire system vulnerable to change by external program code, when we usually expect data inside an object to be private and encapsulated from the outside world.

The Memento pattern attempts to solve this problem in some languages by having privileged access to the state of the object you want to save. Other objects have only a more restricted access to the object, thus preserving their encapsulation. In C#, however, there is only a limited notion of privileged access, but we will use it in this example.

This pattern defines three roles for objects.

  1. The Originator is the object whose state we want to save.
  2. The Memento is another object that saves the state of the Originator.
  3. The Caretaker manages the timing of the saving of the state, saves the Memento, and, if needed, uses the Memento to restore the state of the Originator.

Implementation

Saving the state of an object without making all of its variables publicly available is tricky and can be done with varying degrees of success in various languages. Design Patterns suggests using the C++ friend construction to achieve this access, and the Smalltalk Companion notes that it is not directly possible in Smalltalk. In Java, this privileged access is possible using the package protected mode. The internal keyword is available in C#, but all that means is that any class method labeled as internal will only be accessible within the project. If you make a library from such classes, the methods marked as internal will not be exported and available. Instead, we will define a property to fetch and store the important internal values and make use of no other properties for any purpose in that class. For consistency, we’ll use the internal keyword on these properties, but remember that this linguistic use of internal is not very restrictive.

Sample Code

Let’s consider a simple prototype of a graphics drawing program that creates rectangles and allows you to select them and move them around by dragging them with the mouse. This program has a toolbar containing three buttons—Rectangle, Undo, and Clear—as we see in Figure 26-1.

Figure 26-1. A simple graphics drawing program that allows you to draw rectangles, undo their drawing, and clear the screen

image

The Rectangle button is a toolbar ToggleButton that stays selected until you click the mouse to draw a new rectangle. Once you have drawn the rectangle, you can click in any rectangle to select it, as we see in Figure 26-2.

Figure 26-2. Selecting a rectangle causes “handles” to appear, indicating that it is selected and can be moved.

image

Once it is selected, you can drag that rectangle to a new position, using the mouse, as shown in Figure 26-3.

Figure 26-3. The same selected rectangle after dragging

image

The Undo button can undo a succession of operations. Specifically, it can undo moving a rectangle, and it can undo the creation of each rectangle. There are five actions to which we must respond in this program.

  1. Rectangle button click
  2. Undo button click
  3. Clear button click
  4. Mouse click
  5. Mouse drag

The three buttons can be constructed as Command objects, and the mouse click and drag can be treated as commands as well. Since we have a number of visual objects that control the display of screen objects, this suggests an opportunity to use the Mediator pattern, and that is, in fact, the way this program is constructed.

We will create a Caretaker class to manage the Undo action list. It can keep a list of the last n operations so they can be undone. The Mediator maintains the list of drawing objects and communicates with the Caretaker object as well. In fact, since there could be any number of actions to save and undo in such a program, a Mediator is virtually required so there is a single place to send these commands to the Undo list in the Caretaker.

In this program, we save and undo only two actions: creating new rectangles and changing the position of rectangles. Let’s start with our VisRectangle class, which actually draws each instance of the rectangles.


public class VisRectangle {
      private int x, y, w, h;
      private const int SIZE=30;
      private CsharpPats.Rectangle rect;
      private bool selected;
      private Pen bPen;
      private SolidBrush bBrush;
      //-----
      public VisRectangle(int xp, int yp)              {
             x = xp;                    y = yp;
             w = SIZE;                  h = SIZE;
             saveAsRect();
             bPen = new Pen(Color.Black);
             bBrush = new SolidBrush(Color.Black);
      }
      //-----
      //used by Memento for saving and restoring state
      internal CsharpPats.Rectangle rects {
             get {
                    return rect;
             }
             set {
                    x=value.x;
                    y=value.y;
                    w=value.w;
                    h=value.h;
                    saveAsRect();
             }
      }
      //------
      public void setSelected(bool b) {
             selected = b;
      }
      //-----
      //move to new position
      public void move(int xp, int yp) {
             x = xp;
             y = yp;
             saveAsRect();
      }
      //-----
      public void draw(Graphics g) {
             //draw rectangle
             g.DrawRectangle(bPen, x, y, w, h);

      if (selected) {   //draw handles
      g.FillRectangle(bBrush, x + w / 2, y - 2, , 4);
      g.FillRectangle(bBrush, x - 2, y + h / 2, 4, 4);
      g.FillRectangle(bBrush, x + (w / 2), y + h - 2, 4, );
      g.FillRectangle(bBrush, x + (w - 2),
                           y + (h / 2), 4, 4);
      }
      }
      //-----
      //return whether point is inside rectangle
      public bool contains(int x, int y) {
             return rect.contains (x, y);
      }
      //------
      //create Rectangle object from new position
      private void saveAsRect() {
             rect = new CsharpPats.Rectangle (x,y,w,h);
      }

We also use the same Rectangle class that we developed before that contains Get and Set properties for the x, y, w, and h values and a contains method.

Drawing the rectangle is pretty straightforward. Now, let’s look at our simple Memento class that we use to store the state of a rectangle.


public class Memento       {
      private int x, y, w, h;
      private CsharpPats.Rectangle rect;
      private VisRectangle visRect;
      //------
      public Memento(VisRectangle vrect)              {
             visRect = vrect;
             rect = visRect.rects ;
             x = rect.x ;
             y = rect.y;
             w = rect.w;
             h = rect.h;
      }
      //------
      public void restore() {
             rect.x = x;
             rect.y = y;
             rect.h = h;
             rect.w = w;
             visRect.rects = rect;
      }
}

When we create an instance of the Memento class, we pass it the VisRectangle instance we want to save, using the init method. It copies the size and position parameters and saves a copy of the instance of the VisRectangle itself. Later, when we want to restore these parameters, the Memento knows which instance to which it must restore them, and it can do it directly, as we see in the restore() method.

The rest of the activity takes place in the Mediator class, where we save the previous state of the list of drawings as an integer on the undo list.


public void createRect(int x, int y) {
      unpick();         //make sure none is selected
      if (startRect) {  //if rect button is depressed
             int count = drawings.Count;
             caretakr.Add(count);  //Save list size
             //create a rectangle
             VisRectangle v = new VisRectangle(x, y);
                    drawings.Add(v);//add element to list
             startRect = false;   //done with rectangle
             rect.setSelected(false);    //unclick button
             canvas.Refresh();
      }
      else
             //if not pressed look for rect to select
             pickRect(x, y);
      }
}

On the other hand, if you click on the panel when the Rectangle button has not been selected, you are trying to select an existing rectangle. This is tested here.


public void pickRect(int x, int y) {
      //save current selected rectangle
      //to avoid double save of undo
      int lastPick = -1;
      if (selectedIndex >= 0) {
             lastPick = selectedIndex;
      }
      unpick();  //undo any selection
      //see if one is being selected
      for (int i = 0; i< drawings.Count; i++) {
             VisRectangle v = (VisRectangle)drawings[i];
             if (v.contains(x, y)) {
                    //did click inside a rectangle
                    selectedIndex = i;     //save it
                    rectSelected = true;
                    if (selectedIndex != lastPick) {
                           //but don't save twice
                           caretakr.rememberPosition(v);
                    }
                    v.setSelected(true);    //turn on handles
                    repaint();          //and redraw
             }
      }
}

The Caretaker class remembers the previous position of the rectangle in a Memento object and adds it to the undo list.


public void rememberPosition(VisRectangle vr) {
      Memento mem = new Memento (vr);
      undoList.Add (mem);
}

The Caretaker class manages the undo list. This list is a Collection of integers and Memento objects. If the value is an integer, it represents the number of drawings to be drawn at that instant. If it is a Memento, it represents the previous state of a VisRectangle that is to be restored. In other words, the undo list can undo the adding of new rectangles and the movement of existing rectangles.

Our undo method simply decides whether to reduce the drawing list by one or to invoke the restore method of a Memento. Since the undo list contains both integer objects and Memento objects, we cast the list element to a Memento type, and if this fails, we catch the cast exception and recognize that it will be a drawing list element to be removed.


public void undo() {
      if(undoList.Count > 0) {
             int last = undoList.Count -1;
             object obj = undoList[last];
             try{
                    Memento mem = (Memento)obj;
                    remove(mem);
             }
             catch (Exception) {
                    removeDrawing();
             }
             undoList.RemoveAt (last);
      }
}

The two remove methods either reduce the number of drawings or restore the position of a rectangle.


public void removeDrawing() {
      drawings.RemoveAt (drawings.Count -1);
}
public void remove(Memento mem) {
      mem.restore ();
}

A Cautionary Note

While it is helpful in this example to call out the differences between a Memento of a rectangle position and an integer specifying the addition of a new drawing, this is in general an absolutely terrible example of OO programming. You should never need to check the type of an object to decide what to do with it. Instead, you should be able to call the correct method on that object and have it do the right thing.

A better way to have written this example would be to have both the drawing element and the Memento class have their own restore methods and have them both be members of a general Memento class (or interface). We take this approach in the State example pattern in Chapter 28.

Command Objects in the User Interface

We can also use the Command pattern to help simplify the code in the user interface. You can build a toolbar and create ToolbarButtons in C# using the IDE, but if you do, it is difficult to subclass them to make them into command objects. There are two possible solutions: First, you can keep a parallel array of Command objects for the RectButton, the UndoButton, and the Clear button and call them in the toolbar click routine.

You should note, however, that the toolbar buttons do not have an Index property, and you cannot just ask which one has been clicked by its index and relate it to the command array. Instead, we can use the GetHashCode property of each tool button to get a unique identifier for that button and keep the corresponding command objects in a Hashtable keyed off these button hash codes. We construct the Hashtable as follows.


private void init() {
      med = new Mediator(pic);     //create Mediator
      commands = new Hashtable();  //and Hash table
      //create the command objectsb
      RectButton rbutn = new RectButton(med, tbar.Buttons[0]);
      UndoButton ubutn = new UndoButton(med, tbar.Buttons[1]);
      ClrButton clrbutn = new ClrButton(med);
      med.registerRectButton (rbutn);
      //add them to the hashtable using the button hash values
      commands.Add(btRect.GetHashCode(), rbutn);
      commands.Add(btUndo.GetHashCode(), ubutn);
      commands.Add(btClear.GetHashCode(), clrbutn);
      pic.Paint += new PaintEventHandler (paintHandler);
}

Then the command interpretation devolves to just a few lines of code, since all the buttons call the same click event already. We can use these hash codes to get the right command object when the buttons are clicked.


private void tbar_ButtonClick(object sender,
                    ToolBarButtonClickEventArgs e) {
      ToolBarButton tbutn = e.Button ;
      Command comd = (Command)commands[tbutn.GetHashCode ()];
      comd.Execute ();
}

Alternatively, you could create the toolbar under IDE control but add the tool buttons to the collection programmatically and use derived buttons with a Command interface instead. We illustrate this approach in the State pattern.

The RectButton command class is where most of the activity takes place.


public class RectButton :  Command      {
      private ToolBarButton bt;
      private Mediator med;
      //------
      public RectButton(Mediator md, ToolBarButton tb)     {
             med = md;
             bt = tb;
      }
      //------
      public void setSelected(bool sel) {
             bt.Pushed  = sel;
      }
      //------
      public void Execute() {
             if(bt.Pushed  )
                    med.startRectangle ();
      }
}

Handling Mouse and Paint Events

We also must catch the mouse down, up, and move events and pass them on to the Mediator to handle.


private void pic_MouseDown(object sender, MouseEventArgs e) {
      mouse_down = true;
      med.createRect (e.X, e.Y);
}
//------
private void pic_MouseUp(object sender, MouseEventArgs e) {
      mouse_down = false;
}
//------
private void pic_MouseMove(object sender, MouseEventArgs e) {
      if(mouse_down)
             med.drag(e.X , e.Y);
}

Whenever the Mediator makes a change, it calls for a refresh of the picture box, which in turn calls the Paint event. We then pass this back to the Mediator to draw the rectangles in their new positions.


private void paintHandler(object sender, PaintEventArgs e ) {
      Graphics g =  e.Graphics ;
      med.reDraw (g);
}

The complete class structure is diagrammed in Figure 26-4.

Figure 26-4. The UML diagram for the drawing program using a Memento

image

Consequences of the Memento

The Memento provides a way to preserve the state of an object while preserving encapsulation in languages where this is possible. Thus, data to which only the Originator class should have access effectively remain private. It also preserves the simplicity of the Originator class by delegating the saving and restoring of information to the Memento class.

On the other hand, the amount of information that a Memento has to save might be quite large, thus taking up fair amounts of storage. This further has an effect on the Caretaker class that may have to design strategies to limit the number of objects for which it saves state. In our simple example, we impose no such limits. In cases where objects change in a predictable manner, each Memento may be able to get by with saving only incremental changes of an object’s state.

In our example code in this chapter, we have to use not only the Memento but the Command and Mediator patterns as well. This clustering of several patterns is very common, and the more you see of good OO programs, the more you will see these pattern groupings.

Thought Question

Mementos can also be used to restore the state of an object when a process fails. If a database update fails because of a dropped network connection, you should be able to restore the data in your cached data to their previous state. Rewrite the Database class in the Façade chapter to allow for such failures.

Program on the CD-ROM

image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset