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