Starting Point Analysis
In our project requirement, a user can infinitely undo or redo his updating of the spreadsheet. For the other command, such as plotting one part of the spreadsheet is not necessary to undo or redo.
Requirement in this phase
Adding undo and redo future so that user can undo or redo his updating of the spreadsheet. To fulfill this requirement, we can store each command as an object and also the command history.
Command Pattern Overview
The following simple description of command pattern is from the book, Design Patterns. Please see page 233-242 of this book for more details.
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
- declares an interface for executing an operation.
- ConcreteCommand (PasteCommand, OpenCommand)
- defines a binding between a Receiver object and an action.
- implements Execute by invoking the corresponding operation(s) on Receiver.
- Client (Application)
- creates a ConcreteCommand object and sets its receiver.
- Invoker (MenuItem)
- asks the command to carry out the request.
- Receiver (Document, Application)
- knows how to perform the operations associated with carrying out a request. Any class may serve as a Receiver.
Command History Overview
Using the command pattern, we can wrap a command as an object. We use the command history to support arbitrary-level undo and redo. The following simple description of command history is from the book, Design Patterns.
Conceptually, the command history looks like the following graph, each circle represents a command object.
In this case the user has issued four commands. The leftmost command was issued first, followed by the second-leftmost, and so on until the most recently issued command, which is rightmost. The line marked "present" keeps track of the most recently executed (and unexecuted) command.
To undo the last command, we simply call Unexecute on the most recent command:
After unexecuting the command, we move the "present" line one command to the left. If the user chooses undo again, the next-most recently issued command will be undone in the same way, and we're left in the state depicted here:
You can see that by simply repeating this procedure we get multiple levels of undo. The number of levels is limited only by the length of the command history.
To redo a command that's just been undone, we do the same thing in reverse. Commands to the right of the present line are commands that may be redone in the future. To redo the last undone command, we call Execute on
the command to the right of the present line:
Then we advance the present line so that a subsequent redo will call redo on the following command in the future.
Of course, if the subsequent operation is not another redo but an undo, then the command to the left of the present line will be undone. Thus the user can effectively go back and forth in time as needed to recover from errors
UML Design in this phase
The class diagram for this phase is shown in the following. In the diagram, the TxtInterface class is the Client class in the structure of the command pattern.
Steps in this phase
After this phase, we have our prototype 8, you can issue '%python txtinterface' to run the program. a running example is here.
- step 1: Implement the command pattern without undo and redo feature. In my implementation, I only wrap the 'set' command, which is the command for editing spreadsheet, as an SetCommand object since it relates to the undo or redo feature. We can also do it for all the commands that support it by TxtInterface object.
- step 2: Add undo and redo method for Invoker class. Since the SetCommand object is undoable. we have to store the state for undoing the command prior to invoking execute. The state here refers to that of the spreadsheet. It is impossible to store the whole spreadsheet as a state since it will consume a lot of memory. We only need to store a cell's previous expression for undoing the command. The expression can be converted from a tree object as stated in step 2 of Observer, Interpreter and Iterator Patterns. Undoing the command can be done by simply assigning this expression as a new expression, which execute a new commands again. This is slightly different from the command pattern, but it is very simple. If we save the state for undoing a command, we may have to save all the changes due to a change of a cell's expression or value even if we don't save the whole state of the spreadsheet. This also tells us a pattern is not a panacea, we should adapt it to our project.
Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides Design Patterns Elements of Reusable Object-Oriented Software Addison-Wesley, 1995