Wednesday, December 9, 2015

Non-Trivial Undo in the Command Pattern


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 ON
Then 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 OFF
I 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