n real life, sequences aren't easy to control. Once something is set in motion it is difficult to manipulate discrete portions of that movement. And of course, in real life it's impossible to undo a movement. But in programming, sequences aren't so elusive. If you plan correctly, you can define actions and then control them however you like. And one very useful tool in doing so is the Command Behavior pattern.
Defining the Command Classes
A common way to implement the Command Behavior is to define a base class (or interface) with a Do and Undo method. Undo 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. |
| 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. |
Reviewing the Simple Stack Representation
The techniques in this article require you some familiarity with the stack representation. When I was at Michigan State University as an undergrad we had to implement stacks, and they seemed very hard at the time. Beginning programmers will benefit from a quick review of stacks here; they are easily devised in VB6 from a collection and can be very useful.
A stack is a data structure that follows a last in first out rule (pronounced LIFO) supported by two basic behaviors called push and pop. To add something to a stack you push it, and to remove something you pop it. The last thing you added by calling push will be the first thing you get when you call pop. For example, if we have a stack of integers and pushed 1, 2, 3, 4, 5, and then called pop five times we would get 5, 4, 3, 2, and 1. Hence, stacks are good data structures for back tracking and, interestingly enough, are fundamental to the way your CPU tracks the path your code takes as it runs.
However, a collection can easily be a stack simply based on where you insert at and remove items from. By inserting and removing at the end of the collection very little support code is needed to devise a stack. For example, to implement push call collection.Add; to implement pop call collection.Remove(collection.Count). Simply using the tail end of the collection as the front of the data structure effectively converts a collection into a simple stack. You can see an example of this in Listing 2 in the PushStack and PopStack methods.
Finally, there are several excellent revisions you could make to the example code in this article. Moving the collection, PushStack and PopStack to a Stack class would clean up the form and make a nice general Stack class. Improving the design of code like this is called refactoring. Learning to refactor code is an excellent complement to learning about patterns. You can learn more about Refactoring in Martin Fowler's Refactoring: Improving the Design of Existing Code from Addison-Wesley and more about patterns in Erich Gamma and company's book Design Patterns, also from Addison-Wesley.
The Quality of Your Tools
In this article you learned about a tool, the Command behavior pattern. If you find that invoked behaviors need to be undoable, or that your invocation code is becoming too complex then I hope you remember this simple but powerful tool. Other concepts discussed hereUML, patterns, and refactoringthese, too, are tools.
There is no one perfect tool for every job. (Remember the adage 'if all you have is a hammer then every problem is a nail'?) My dad used to say that a craftsman is known by the quality of his tools. In other words, the more tools you have the more you can build.
| DevX is a division of Jupitermedia Corporation © Copyright 2007 Jupitermedia Corporation. All Rights Reserved. Legal Notices |