Chapter 21. The Memento Pattern

In this chapter, we discuss how to use the Memento pattern to save data about an object so that you can restore it later. For example, you might like to save the color, size, pattern or shape of objects in a draughting or painting program. Ideally, you can 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 still want 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 positions 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 yet this information is not readily available. This saving and restoration of data is common in systems that need to support Undo commands.

If all of the data that describe an object is available in public variables, saving in some external store is not difficult. However, making this data public makes the entire system vulnerable to change by external program code, when you usually expect data inside an object to be private and encapsulated from the outside world.

The Memento pattern attempts to solve this problem by having privileged access to the state of the object that you want to save. Other objects have only a more restricted access to the object, thus preserving their encapsulation. This pattern defines the following three roles for objects:

  1. Originator. the object whose state you want to save.

  2. Memento. another object that saves the state of the Originator.

  3. 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. Smalltalk Companion notes that it is not directly possible in Smalltalk. In Java, this privileged access is possible using a little-known and seldom-used protection mode. Variables within a Java class can be declared as

  • private,

  • protected,

  • public, or

  • [package protected] (which is the default if none is declared).

Java does not allow you to declare variables as package protected, but a variable with no protection declaration is treated as having that level of visibility. Other classes can access public variables, and derived classes can access protected variables. However, another class in the same package can access protected or private-protected variables. It is this last feature of Java that you can use to build Memento objects.

For example, suppose that we have classes A and B declared in the same method.

public class A {
int x, y:
public X () {
x = 5;                            //initialize x
}
//--------------
class B {
public B() {
    A a = new A();               //create an instance of A
    System.out.println (a.x);    //has access to variables in A
  }
}

Class A contains a private-protected variable x. In class B in the same module, we create an instance of A, which automatically initializes x to 5. Class B has direct access to the variable x in class A and can print it out without compilation or an execution error. It is exactly this feature that we will use to create a Memento.

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 (R), Undo (U), and Clear (C)—as shown in Figure 21.1.

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

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

The Rectangle button is a JToggleButton 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 shown in Figure 21.2.

Selecting a rectangle causes handles that indicate that it is selected and can be moved to appear.

Figure 21.2. Selecting a rectangle causes handles that indicate that it is selected and can be moved to appear.

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

The same selected rectangle after dragging.

Figure 21.3. The same selected rectangle after dragging.

The Undo button can undo a succession of operations. In the current case, it can undo moving a rectangle and undo the creation of each rectangle.

We need to respond to five actions 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 similarly. Because 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 the way that this program is constructed.

We'll create a Caretaker class to manage the Undo action list; it can keep a list or the last n operations so that they can be undone. The Mediator maintains the list of drawing objects and communicates with the Caretaker object. In fact, because a program could contain any number of actions to save and undo, a Mediator is virtually required so that 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 positions of rectangles. Let's start with the visRectangle class, which actually draws each instance of the rectangles.

public class visRectangle {
    int        x, y, w, h;        //package-protected
    private Rectangle rect;
    private boolean    selected;
    public visRectangle(int xpt, int ypt)    {
        x = xpt;    y = ypt;    //save the location
        w = 40;     h = 30;     //use the default size
        saveAsRect();
    }
    //--------------
    public void setSelected(boolean b)   {
        selected = b;
    }
    //--------------
    private void saveAsRect()   {
    //convert to rectangle so that we can use the contains method
        rect = new Rectangle(x-w/2, y-2/2, w, h);
    }
    //--------------
    public void draw(Graphics g)   {
        g.drawRect(x, y, w, h);
        if (selected)    {    //draw handles
            g.fillRect(x+w/2, y-2, 4, 4);
            g.fillRect(x-2, y+h/2, 4, 4);
            g.fillRect(x+y/2, y+h-2; 4, 4);
            g.fillRect(x+w-2, y+h/2, 4, 4);
        }
    }
    //--------------
    public boolean contains(int x, int y)   {
        return rect.contains(x, y);
    }
    //--------------
    public void move(int xpt, int ypt)   {
        x = xpt; y = ypt;
        saveAsRect();
    }
}

Drawing the rectangle is straightforward. Now, let's look at the Memento class, which must be contained in the same package as visRectangle.java and thus will have access to the position and size variables.

public class Memento {
    visRectangle rect;
    //saved fields- remember internal fields
    //of the specified visual rectangle

    private int x, y, w, h;

    public Memento(visRectangle r) {
        rect = r;
        x = rect.x;  y = rect.y;
        w = rect.w;  h = rect.h;
    }
    //--------------
    public void restore () {
        //restore the internal state of
        //the specified rectangle
        rect.x = x;  rect.y = y;
        rect.h = h;  rect.w = w;
    }
}

When we create an instance of the Memento class, we pass it the visRectangle instance that we want to save. 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 must be restored and can do it directly, as shown 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 the Integer on the undo list.

    public void createRect(int x, int y) {
        unpick();    //make sure no rectangle is selected
        if (startRect)    {    //if the Rect button is depressed
            Integer count = new Integer(drawings.size());
            caretaker.addElement(count);   //save the previous size
            visRectangle v = new visRectangle(x, y);
            drawings.addElement(v);        //add a new element
            startRect = false;             //done with a rectangle
            rect.setSelected(false);       //unclick the button
            canvas.repaint();
        }else
            pickRect(x, y);    //if not pressed look for the Rect
    }

We also save the previous position of a rectangle before moving it into a Memento.

    public void rememberPosition() {
        if (rectSelected) {
            Memento m = new Memento (selectedRectangle);
            caretaker.addElement(m);
            repaint();
        }
    }

The undo method simply decides whether to reduce the drawing list by one or to invoke the restore method of a Memento.

public void undo() {
        if (undoList.size() > 0) {
            //get the last element in the undo list
            Object obj = undoList.lastElement();
            undoList.removeElement(obj);    //and remove it
            if (obj instanceof Integer)
               remove ((Integer)obj); //remove the integer or],
                                      //Memento
            else
                remove((Memento)obj);
        }
}

The complete class structure is diagrammed in Figure 21.4.

The class structure for the drawing program using the Memento pattern.

Figure 21.4. The class structure for the drawing program using the Memento pattern.

Consequences of the Memento Pattern

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

  2. However, the amount of data that a Memento must save might be quite large, thus taking up a fair amount of storage. This also affects the Caretaker class (here, the Mediator), which might have to design strategies to limit the number of objects for which it saves state. In the example in this chapter, we imposed no such limits. When objects change in a predictable manner, each Memento might be able to get by with saving only incremental changes of an object's state.

Thought Question

  1. The Memento pattern can be used to restore an object's state 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 pattern (Chapter 13) to allow for such failures.

Programs on the CD-ROM

Programs Description

MementoMemDraw.java

Allows you to draw and move rectangles and undo these operations.
..................Content has been hidden....................

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