Tuesday, December 8, 2015

Limited Command Callbacks


I remain fascinated by command callbacks after last night's exploration. I can count the number of times that I have used an object-oriented command pattern on one hand. But I have the feeling that I have used callback commands numerous times.

The Dart command callbacks from last night look like:
  // Concrete command instances
  var switchUp = (){ lamp.turn('ON'); },
    switchDown = (){ lamp.turn('OFF'); };
The receiver and action are embedded right in the callback.

The object-oriented command on the other hand looks like:
  // Concrete command instances
  var switchUp = new OnCommand(lamp),
    switchDown = new OffCommand(lamp);
In the OO version, the receiver is supplied by the client as it is in the callback version, but the linking of the two occurs in the command class:
// Concrete Command
class OnCommand implements Command {
  Light light;
  OnCommand(this.light);
  void call() { light.turn('ON'); }
}
The Gang of Four book mentions callbacks as a potential implementation for the command pattern in the Applicability section of the pattern description. I had never thought of that as a command, but it works as a drop-in replacement for the OO version.

Also in the Applicability section is that commands ought to work in queues. So let's see how the callback version fairs in a queue. I map a list of strings into a queue as:
  // Command queue
  var queue = commands.map((command){
    if (command == 'on') return switchUp;
    if (command == 'off') return switchDown;
    print("Can only switch on or off");
  }).toList();
I can asynchronously run the commands with Timer.periodic():
  var index = 0;
  new Timer.periodic(new Duration(seconds: 2), (timer){
    s.storeAndExecute(queue[index++]);
  });
That works both with the OO version and the callback version. So there is at least one bullet point under the Applicability section that works with callbacks.

What about the next one, which is undo? Well, with my original implementation, it actually works both with both OO and callback versions:
  var index = 0;
  new Timer.periodic(new Duration(seconds: 2), (timer){
    s.storeAndExecute(queue[index++]);
    if (index >= queue.length) {
      timer.cancel();
      s.undo();
    }
  });
The problem is that I am not really undoing anything, just playing the commands back in reverse (both storeAndExecute() and undo() invoke the same call() method):
// Invoker
class Switch {
  List _history = [];

  // void storeAndExecute(Command c) {
  void storeAndExecute(Function c) {
    c.call();
    _history.add(c);
  }

  void undo() {
    print('--');
    _history.
      reversed.
      forEach((c) { c.call(); });
  }
}
That is why this works for both the callback version in addition to the OO version. But if I want this done right, the concrete command objects need to specify how they undo their execution:
// Concrete Command
class OnCommand implements Command {
  Light light;
  OnCommand(this.light);
  void call() { light.turn('ON'); }
  void undo() { light.turn('OFF'); }
}
With that, I can rewrite the invoker undo as:
class Switch {
  // ...
  void undo() {
    _history.
      reversed.
      forEach((c) { c.undo(); });
  }
}
When I run the OO version, undoing the entire light switch script actually reverses each step from the original:
$ ./bin/press_switch.dart on on off on
Light ON
Light ON
Light OFF
Light ON
--
Light OFF
Light ON
Light OFF
Light OFF
I suppose that the callback version could return an undo callback, but that is venturing into callback spaghetti territory. I think it safe to say that the callback command will not work for undo.

In fact, I do not believe that the other two bullet points from the GoF description will work with callbacks either. There is no way to load/store callbacks. Building higher lever systems on top of callbacks is called insane—or Node.js (I kid!). So I think I have a better idea of the limitations of command callbacks. I do think I could use a better example to illustrate the point—on/off state switching it a tad simplistic. I will plan on starting that tomorrow.

Play with this code on DartPad: https://dartpad.dartlang.org/6576696232e0b6c0109d.


Day #27

No comments:

Post a Comment