Defining the Command Classes
A common way to implement the Command Behavior is to define a base class (or interface) with a Do
performs the opposite action of the Do
behavior. It doesn't really matter what the Do
behavior is; it can be anything. ('Do' is a reserved word in VB6, so I will substitute the word 'Execute' as my method name.)
For my example I implemented four movement commands. Each command invokes a move operation in one of four directions: up, down, left, or right. The Undo
behavior for each of these commands invokes a move command in the opposite direction. Obviously, I am not limited to two-dimensional, linear directions. I could simulate a third dimension or base movements on trigonometric algorithms. For now I'll focus on the command classes.
The reason for a common base class is that the code then can polymorphically invoke either the Do or the Undo action indifferent to the specific instance of the command object. Listing 1 shows the base command and implementation of all four derived command classes. Because VB6 does not support class inheritance, interface inheritance is employed.
Note that in Listing 1 an I-prefix is used for the ICommand interface. This is a popular prefix notation for interfaces and is used widely in several languages. It was devised to help readers recognize that the module contained definitions only. Also note that the Implements statement was used in all of the directional commands. This ensures that each class has the ICommand interface at a minimum. The result is that I can declare an ICommand variable and assign it to any instance of a class that implements ICommand.
Finally, it is worth pointing out that each command maintains a reference to a specific Form, Form1. The reason is that Form1 contains information about its boundaries and the control I want to manipulate with commands.
Creating the GUI
The GUI, represented by Form1, contains an image control with a bitmap of a ball on it. The form also contains a collection object that I will treat like a stack of prior commands. By adding each command to the end of the collection and "popping" from the end of the collection I can trace backward in my command execution. In essence, the collection plays the role of a stack. I push commands after execution and pop to invoke the Undo behavior.
To demonstrate the Undo behavior I defined an Edit|Undo menu and an Edit|Rewind behavior. Undo pops one command from the stack and calls Undo. Rewind pops all commands, calling Undo on each. Listing 2 contains the code for Form1.
|Figure 1. Classes in Command: A UML class diagram that depicts the relationships in our implementation of the Command pattern.|
Next I want to tie the relevant directional arrow keys to each command by setting the Form's KeyPreview
property to True
and creating the command object associated with that key. For example, vbKeyUp
should create an UpCommand
object. Then I process the command. (I could also use the Factory Creational pattern to move creation of the command objects into the command class itself.)
ProcessCommand is defined to assign a reference to Form1 to the command's Form property, to execute the command, and then to push the command on the collection-cum-stack. You don't have to implement a stack, but if you don't keep track of the commands in the order they were executed then it will be impossible to use the Undo behavior.
Figure 1 shows a UML model illustrating the relationship between the various classes in the sample program.
Checking Your Work in UML
If you know UML then you'll recognize that Figure 1 basically summarizes the article thus far. If you don't know UML (it is a good skill to acquire) simple class diagrams like this one are not that hard to follow. Lines represent relationships. Diamonds represent whole and part relationships, that is, aggregation. The whole has the diamond attached to it and the part does not. Directional arrows represent associations, which are roughly similar to aggregate relationships, and triangles represent inheritance, where the thing inherited from has the triangle attached to it. Dashed, directional lines indicate a dependency or something needed.
Reading Figure 1, then, logically, LeftCommand, RightCommand, DownCommand, and UpCommand inherit from ICommand. Commands have an association to but don't own the Formrepresented by their Form properties. The Form is dependent on commands because that is how the Form's behaviors are invoked, and the Form has an aggregate relationship with the collection because the Form is responsible for the life of the collection.
These definitions are not precisely technical in the UML. For example, we can differentiate between inheritance, called generalization, and interface inheritance, called realization, but the explanation is pragmatically correct.
|Author's Note: For more information on UML basics, pick up a copy of my upcoming book UML DeMystified from McGraw-Hill/Osborne coming in the spring of 2005.
The UML class diagram in Figure 1
shows me only a static view of the classes involved. A sequence diagram could be used to represent interactions between these elements. To grasp the behavior, try stepping through the code in the VB6 IDE debugger.