Copyright © , 2002 by James W Cooper
301
Figure 22-4 – Class diagram of CommandHolder appraoch
Providing Undo
Another of the main reasons for using Command design patterns is that
they provide a convenient way to store and execute an Undo function.
Each command object can remember what it just did and restore that state
Copyright © , 2002 by James W Cooper
302
when requested to do so if the computational and memory requirements
are not too overwhelming. At the top level, we simply redefine the
Command interface to have three methods.
public interface Command {
void Execute();
void Undo();
bool isUndo();
}
Then we have to design each command object to keep a record of what it
last did so it can undo it. This can be a little more complicated than it first
appears, since having a number of interleaved Commands being executed
and then undone can lead to some hysteresis. In addition, each command
will need to store enough information about each execution of the
command that it can know what specifically has to be undone.
The problem of undoing commands is actually a multipart problem. First,
you must keep a list of the commands that have been executed, and
second, each command has to keep a list of its executions. To illustrate
how we use the Command pattern to carry out undo operations, let’s
consider the program shown in Figure 22-5 that draws successive red or
blue lines on the screen, using two buttons to draw a new instance of each
line. You can undo the last line you drew with the undo button.
Copyright © , 2002 by James W Cooper
303
Figure 22-5 – A program that draws red and blue lines each time you click the Red
and Blue buttons
If you click on Undo several times, you’d expect the last several lines to
disappear no matter what order the buttons were clicked in, as shown in
Figure 22-6.
Copyright © , 2002 by James W Cooper
304
Figure 22-6– The same program as in Figure 22-5 after the Undo button has been
clicked several times
Thus, any undoable program needs a single sequential list of all the
commands that have been executed. Each time we click on any button, we
add its corresponding command to the list.
private void commandClick(object sender, EventArgs e) {
//get the command
Command comd = ((CommandHolder)sender).getCommand ();
undoC.add (comd); //add to undo list
comd.Execute (); //and execute it
}
Further, the list to which we add the Command objects is maintained
inside the Undo command object so it can access that list conveniently.
public class UndoComd:Command {
private ArrayList undoList;
public UndoComd() {
undoList = new ArrayList ();
}
//-----
public void add(Command comd) {
Copyright © , 2002 by James W Cooper
305
if(! comd.isUndo ()) {
undoList.Add (comd);
}
}
//-----
public bool isUndo() {
return true;
}
//-----
public void Undo() { }
//-----
public void Execute() {
int index = undoList.Count - 1;
if (index >= 0) {
Command cmd = (Command)undoList[index];
cmd.Undo();
undoList.RemoveAt(index);
}
}
}
The undoCommand object keeps a list of Commands, not a list of actual
data. Each command object has its unDo method called to execute the
actual undo operation. Note that since the undoCommand object
implements the Command interface, it, too, needs to have an unDo
method. However, the idea of undoing successive unDo operations is a
little complex for this simple example program. Consequently, you should
note that the add method adds all Commands to the list except the
undoCommand itself, since we have just defined undoing an unDo
command as doing nothing. For this reason, our new Command interface
includes an isUndo method that returns false for the RedCommand and
BlueCommand objects and true for the UndoCommand object.
The redCommand and blueCommand classes simply use different colors
and start at opposite sides of the window, although both implement the
revised Command interface. Each class keeps a list of lines to be drawn in
a Collection as a series of DrawData objects containing the coordinates of
each line. Undoing a line from either the red or the blue line list simply
means removing the last DrawData object from the drawList collection.