Design patterns are interesting in themselves, but it’s useful to be able to move between patterns while refactoring a system. We’ll look at how you can move between command, interpreter, and visitor. |
Many interactive programs allow a user to perform one of a number of actions. The code for this tends to begins its life scattered around the system.
This usually shows up in one of two forms. In the first form, the code is in a big switch or if statement:
if (control.equals("Save")) {
// save the file
} else if (control.equals("Cut")) {
// cut the selection
} else {
// etc. - on and on
}
This is bad form because the handler routine becomes a bottleneck for changes. Any new operation means changing code rather than adding code; thus changes in one feature might break completely unrelated ones.
The second form has the code attached to widgets. For example:
cutMenuItem.addMenuListener (new CutListener());
:
public class CutListener {
public void handle(MenuEvent e) { /* cut code */}
}
This code is better in that it keeps separate functions apart, but it’s still flawed because GUI code tends to be intermingled with Model code.
Command
There are several forces that might make you wish for a more general solution:
- A need to trigger the same action from multiple places (e.g., from the menu or from a button). The “CutListener” code above might work, except that it’s customized for menus rather than some other widget type.
- A need to support macros. This is a similar problem, in that there may be two things trying to perform an action: the original button click or the macro subsystem. The macro code can be even more complicated if it’s dealing with a sequence of commands.
- A need for “undo” support. Again, there are two places that might want to perform an operation: the original button or menu item, and the undo/redo subsystem.
The bits of “action” code tend to represent functions, but we may find it easier to “objectify” them. The Command pattern exists to do this.
Suppose we have this code:
:
Function A
:
Function B
:
(Make sure to test after each step.)
- Create a parent Command class:
Command {
abstract void execute();
} - For each bit of code (e.g., Function A), introduce a new class that’s a subclass of Command (e.g., ACommand):
ACommand extends Command
Do this one command at a time.
Compile and test the new class. - Copy the function code to the subclass’ execute() method:
ACommand {
execute() { /* Function A body */ }
} - Replace the original site for function A:
:
new ACommand().execute();
:
Function B
:
Compile and test. - Repeat for each function.
- Determine whether those call sites each need to create a new instance of the command (subclass) each time. You may find you can create an instance of the command and pass it around. (For example, a menu and button might be able to share the same instance of a command object.)
At this point, you’re ready to introduce macros, redo, etc. as in the standard Command pattern.
Command and Composite
For macros, or just convenience, it can be convenient to build commands out of other commands. For example, some systems let the user record a sequence of commands as a saved action.
In this situation, make Command a Composite:
public class ACommand extends Command {...}
public class CompositeCommand extends Command {
Vector commands; // etc. ...
public void execute() {
// for each element c of commands, do c.execute()
}
}
The CompositeCommand represents a command that can hold and execute a sequence of other commands.
Interpreter
At this point, we’ve almost become an interpreter. There are three key differences:
- Interpreter generally references an external context
- The composite types are usually broadened to more than simple sequences (e.g., separate composites for “if” versus “repeat”).
- Interpreters typically don’t support “undo”, as the context becomes so big it’s not feasible to hold all the state in memory.
How do you get a language into the form that Interpreter expects to work with? There are typically two approaches:
- Programmatic: The caller is responsible for building the object structure it wants to interpret.
- Parsed: There is a separate routine that takes a string (the code to be interpreted), and it is responsible for understanding the string so it can create the objects.
If you’re setting out on a plan to invent a new language, you should first consider whether an existing interpreter could handle what you need (perhaps with a new library or some minor tweaking).
To Visitor
You have this:
public class Command {
abstract void execute();
}
public class ACommand extends Command ...
public class BCommand extends Command ...
But you find there’s another process that wants to look at the same nodes:
public class Command {
abstract void execute();
abstract void display();
}
Then another function comes along: it doesn’t need to change the command structure, but needs to perform a new function. You get tired of updating all the command nodes again. You may decide to move to the Visitor pattern. (See Design Patterns by Gamma et al. for the gory details.)
- Create a Visitor class. For each command class that exists, give it an accept method:
public void visit(ACommand) {}
public void visit(BCommand) {}
Compile and test. - Add an accept() method to each Command node. Each one should have an implementation like this:
public void accept (Visitor v) {
v.visit(this);
}
Compile and test. - For each function F on the Command nodes, create a subclass of Visitor, FVisitor. Copy its body from the corresponding command to the corresponding method. Compile and test.
- Create a new test of the new Visitor. It should create an instance of the visitor, and call the command’s accept() method. Compile and test. Create more tests as needed. This should function exactly as the original. Compile and test.
- Working one at a time, turn all calls to the function into calls on the visitor. Compile and test.
- There should be nothing left calling the function. Delete it from each command class. Compile and test.
- Repeat for the other functions.
What you’re left with is the new Visitor form.
Warning
Here’s my warning: Use Visitor with care.
I’ve migrated to Visitor twice in my life. I’ve ended up ripping it back out both times.
Why? Because Visitor is not happy when the command structure changes. Even when I think I’ve got that nailed down, it turns out that the command structure is what changed, but I wasn’t adding more functions.
Is Visitor a bad or useless pattern? No – it’s chasing something important. I have it clustered with the idea of “double dispatch” (depending on two types to know what to do) and mixins (assembling code from small bits of functionality). I think of “futuristic” approaches like aspect-oriented programming [http://www.parc.xerox.com/csl/projects/aop/] or subject-oriented programming [http://www.research.ibm.com/sop/] as embedding visitor in a way that the system can automatically take care of changing the commands or the visitors. Visitor in Java feels about as clumsy as a simulated object in C: it can be done manually, but only in a disciplined way.
Returning
It’s not hard to go back from Visitor to Interpreter:
- Work one Visitor at a time. Add a function to the command class corresponding to the Visitor. Compile and test.
- Copy the corresponding visitor method code to the method on the various Command subclass objects. Compile and test.
- Add new tests for the new command objects. Compile and test.
- Working one at a time, replace each Visitor call site with a call to the corresponding function. Compile and test.
- Once all uses of a Visitor are gone, delete it. Compile and test.
- Continue until all Visitors have disappeared.
Conclusion
We’ve demonstrated how code can be changed to use a Command pattern, then an Interpreter, and a Visitor, and back.
Resources
- From 0 to Composite (and Back Again)
- Design Patterns: Elements of Reusable Object-Oriented Software, Gamma et al., 1995.
- Refactoring: Improving the Design of Existing Code, Martin Fowler.
[Written 9-6-2000; minor edits 12-7-2000.]