Friday, December 18, 2015

Simple Commands in a Complex Command System


I suffer an abundance of commands. I am trying to keep my exploration of the command pattern simple as I prepare for the corresponding entry in Design Patterns in Dart. My simple code has 10 commands:
  // Concrete command instances
  var moveNorth = new MoveNorthCommand(robot),
    moveSouth = new MoveSouthCommand(robot),
    moveEast = new MoveEastCommand(robot),
    moveWest = new MoveWestCommand(robot),
    danceHappy = new DanceHappyCommand(robot),
    startRecording = new StartRecordingCommand(camera),
    stopRecording = new StopRecordingCommand(camera),
    undo = new UndoCommand(history),
    redo = new RedoCommand(history);
I can likely eliminate several of those for a terser discussion, but this still begs the question, what the heck would I do with this in live code?

The problems is not so much the 10 instances of command objects. If there are 10 buttons in the UI (which is a problem for another book), then I need 10 commands. The trouble here is that I had to manually declare all 10 of those commands, each with varying degrees of complexity:
class MoveNorthCommand implements Command {
  Robot robot;
  MoveNorthCommand(this.robot);
  void call() { robot.move(Direction.NORTH); }
  void undo() { robot.move(Direction.SOUTH); }
}
The Gang of Four book suggests the use of C++ template classes to overcome class overload. As long as a command is simple enough (receiver + action, no undo), then C++ templates seems a nice solution. One template declaration can generate any number of simple command types.

There are no template classes in Dart. There is no way to dynamically generate a class at run or compile time. Either the class is hard-coded before compiling or it does not exist. So what are my options if I want to avoid command class explosion?

One option comes from the GoF themselves. They characterize commands as an object-oriented replacement for commands. Callbacks cannot support undo, but I only need a solution for simple commands, like making my robot say a word or phrase:
  var btnSayHi = new Button("Hi!", (){ robot.say("Hi!"); });
  var btnScare = new Button("Scare Robot", (){ robot.say("Ahhhhh!"); });

  btnSayHi.press();
  btnScare.press();
I have cleverly been naming my command execution method call(), which is what the Button's press() method invokes:
class Button {
  String name;
  Command command;
  Button(this.name, this.command);

  void press() {
    print("[pressed] $name");
    command.call();
  }
}
Functions in Dart respond to call() by invoking the function. So this quick and dirty solution works:
$ ./bin/play_robot.dart
[pressed] Hi!
Hi!
[pressed] Scare Robot
Ahhhhh!
It works, but there are at least two problems. First is that the command property in Button is declared as a Command type. Supplying a function instead results in analyzer errors:
$ dartanalyzer bin/play_robot.dart lib/robot.dart
Analyzing [bin/play_robot.dart, lib/robot.dart]...
[warning] The argument type '() → dynamic' cannot be 
          assigned to the parameter type 'Command' 
(/home/chris/repos/design-patterns-in-dart/command/bin/play_robot.dart, line 34, col 36)
[warning] The argument type '() → dynamic' cannot be
          assigned to the parameter type 'Command' 
(/home/chris/repos/design-patterns-in-dart/command/bin/play_robot.dart, line 35, col 44)
2 warnings found.
I can get around this by declaring that the command property must be a Function:
class Button {
  String name;
  Function command;
  Button(this.name, this.command);
  // ...
}
And then declaring that the Command interface implements Function:
abstract class Command implements Function {
  void call();
}
That give me no type analysis errors:
$ dartanalyzer bin/play_robot.dart lib/robot.dart
Analyzing [bin/play_robot.dart, lib/robot.dart]...
No issues found
And my robot can still perform simple speech commands:
$ ./bin/play_robot.dart
[pressed] Hi!
Hi!
[pressed] Scare Robot
Ahhhhh!
The other problem with this approach is undo history. I specifically do not want to store these simple commands in my history since they do not support undo. I have yet to ignore these so they get added all the same. If I attempt an undo after making the robot speak:
  // ...
  btnUp.press();

  btnSayHi.press();
  btnScare.press();

  btnUndo.press();
  // ...
Then I get an exception from calling undo() on my callback command:
// ...
[pressed] Up
  I am moving Direction.NORTH
[pressed] Hi!
Hi!
[pressed] Scare Robot
Ahhhhh!
[pressed] Undo
Undoing Closure: () => dynamic
Unhandled exception:
Closure call with mismatched arguments: function 'undo'
I am adding to history in the button press() method:
class Button {
  String name;
  Function command;
  Button(this.name, this.command);

  void press() {
    print("[pressed] $name");
    command.call();
    History.add(command);
  }
}
I can add a guard clause to the History.add() static method to prevent adding callback commands to the undo history. This is a little tricky for functions, however. First, I cannot check to see if the command is a Function because all my commands are now functions:
class History {
  // ...
  static void add(Function c) {
    // Nothing gets added now :(
    if (c is Function) return;

    // Add to singleton history instance
    _h._undoCommands.add(c);
  }
  // ...
}
Comparing the runtimeType is not much easier. The runtimeType of an anonymous function is not, in fact, Function:
class History {
  // ...
  static void add(Function c) {
    // Matches nothing, callback functions still get added :(
    if (c.runtimeType == Function) return;

    // Add to singleton history instance
    _h._undoCommands.add(c);
  }
  // ...
}
Oddly, even if I compare the runtimeType to the runtimeType of a actual function, I still do not get a proper guard clause:
class History {
  // ...
  static void add(Function c) {
    // Not equal for some reason, all callbacks still added to undo history :(
    if (c.runtimeType == (){}.runtimeType) return;

    // Add to singleton history instance
    _h._undoCommands.add(c);
  }
  // ...
}
What does work is converting the runtimeType to a String for comparison:
class History {
  // ...
  static void add(Function c) {
    // Both are '() => dynamic'
    if (c.runtimeType.toString() == (){}.runtimeType.toString()) return;

    // Add to singleton history instance
    _h._undoCommands.add(c);
  }
  // ...
}
With that, I have a relatively complex command pattern implementation that supports undo (and redo) history, yet still allows for very simple command definitions via callbacks. In the end it did not require too much effort. I may not have a full-blown class like a template might have produced, but this still seems an acceptable approach.

Play with the code on DartPad: https://dartpad.dartlang.org/c9f076a1c2df94fc3243.


Day #37

5 comments:

  1. You could do something similar to a C++ template. Like the code I've created here

    https://dartpad.dartlang.org/f26c7bd9b20f6254dd0e

    Of course that might be what you're thinking of doing next anyway. You could even look into Darts Generics for a template like solution, you just can't invoke methods on class instances, like you can with the GoF example, using a C++ template solution.

    ReplyDelete
    Replies
    1. I was planning on doing something like that SimpleCommand, but ran out of time :D


      The only thing I'm unclear on is why the GoF really wanted new types for each command instead of new instances of a simple command like that. Types don't really seem to matter in this pattern.

      Anyhow, you know I love my mirrors, so I'll be looking into generics next :)

      Delete
    2. Yes I know how vain you are :-P

      Looking forward to it

      Delete
    3. Yes I know how vain you are :-P

      Looking forward to it

      Delete