Wither undo? Contemplating that question in regards to the command pattern, I could not decide between the receiver and the invoker. Naturally, the Gang of Four book opts for the concrete command object. Perplexed, I delve into that tonight.
Lacking a better example, I am still working with a Dart take of the classic light switch example for the command pattern. The command object from last night did place the
undo() in the concrete command object, but I felt that I could get away with that in this simplistic example. The "on" command, for instance, inherently knows how to undo switching a light on... by turning it off:// Concrete Command
class OnCommand implements Command {
Light light;
OnCommand(this.light);
void call() { light.turn('ON'); }
void undo() { light.turn('OFF'); }
}But what if the switch supports a third state, say 50% lighting? The power of the command pattern, of course, is that it is easy to add new commands. No existing classes need to change, I just need to add the FiftyCommand:class FiftyCommand implements Command {
Light light;
FiftyCommand(this.light);
void call() { light.turn('50%'); }
void undo() { /* ???? */ }
}Ah, I see how to implement undo in the concrete class. When the command is called, I can save the receivers previous state, then reuse it when undoing:class FiftyCommand implements Command {
Light light;
String _prev;
FiftyCommand(this.light);
void call() {
_prev = light.state;
light.turn('50%');
}
void undo() {
light.turn(_prev);
}
}I again see the weakness of this particular example (even with a third state). The command pattern is likely most appropriate when working with multiple kinds of receivers instead of a single light as I am doing here. The simplicity of working with a single light means that the on, off, and 50% commands can all extend a common baseclass that defines the undo behavior:class LightCommand implements Command {
Light light;
String _prev;
LightCommand(this.light);
void call() {
_prev = light.state;
}
void undo() {
light.turn(_prev);
}
}The on, off, and fifty classes can then extend this class to send the appropriate message to the receiver and rely on the baseclass to store the previous state:class FiftyCommand extends LightCommand {
FiftyCommand(light): super(light);
void call() {
super.call();
light.turn('50%');
}
}With that, if I turn the light on, 50%, off, and on:$ ./bin/press_switch.dart on 50 off on Light ON Light 50% Light OFF Light ONThen undoing everything should move back to off, then 50%, then on, then back to the original state (off). Which is exactly what gets reported:
$ ./bin/press_switch.dart on 50 off on Light ON Light 50% Light OFF Light ON -- Light OFF Light 50% Light ON Light OFFI think that I have a good handle on this, which means that I'm probably ignoring 5 or 6 major implications that I will discover in the next few days. Even if I do have a complete understanding of command-undo, I think that I need a better example to include in Design Patterns in Dart. The switch example is good first pass example, but it lacks legitimate motivation for using this pattern. I will likely try to improve on the example tomorrow.
Play with this code on DartPad: https://dartpad.dartlang.org/c188204c7a4a5194bfe1.
Day #28
No comments:
Post a Comment