Wednesday, December 16, 2015

Command History


So where does history get stored in the command pattern? The Gang of Four book only mentions in passing that the "application" holds the history. I wound up putting history in the command invoker as I was experimenting. That it is looking like a mistake.

The problem is my Dart invoker does more history management than it does invoking:
// Invoker
class Button {
  static List _history = [];
  static List _undoHistory = [];

  String name;
  Command command;
  Button(this.name, this.command);

  void press() {
    print("[pressed] $name");
    command.call();
    _history.add(command);
  }

  static void undo() {
    var h = _history.removeLast();
    print("Undoing $h");
    h.undo();
    _undoHistory.add(h);
  }

  static void redo() {
    var h = _undoHistory.removeLast();
    print("Re-doing $h");
    h.call();
    _history.add(h);
  }
}
Do you see the code that actually invokes commands? Probably not without looking really close because of all those static methods.

It's never a good sign if you have a bunch of static classes and properties like that. My code is begging me to extract it out into a separate class, which I do:
class History {
  List _undoCommands = [];
  List _redoCommands = [];

  void add(Command c) {
    _undoCommands.add(c);
  }

  void undo() {
    var h = _undoCommands.removeLast();
    print("Undoing $h");
    h.undo();
    _redoCommands.add(h);
  }
  void redo() {
    var h = _redoCommands.removeLast();
    print("Re-doing $h");
    h.call();
    _undoCommands.add(h);
  }
}
With that, my button invoker is much, much nicer:
class Button {
  String name;
  Command command;
  Button(this.name, this.command);

  void press() {
    print("[pressed] $name");
    command.call();
  }
}
Now I need to figure out how to get commands into history. Do I alter the press() method of Button to return the command so that client code can store history or do I make the invoker responsible for storing history. I opt for the latter, mostly because I do not want to clutter up my client code with a call to history.add() for each button press.

I do opt to convert the History class into a singleton and its add() method into a static method (static methods do have their place):
class History {
  // ...
  static final History _h = new History._internal();
  factory History() => _h;
  History._internal();

  static void add(Command c) {
    _h._undoCommands.add(c);
  }
  // ...
}
There is never a reason for more than one history object, so a singleton makes sense. Converting the add() method to a static method is a judgment call, but I wanted to be able to invoked History.add() when adding to history in the invoker:
class Button {
  // ...
  void press() {
    print("[pressed] $name");
    command.call();
    History.add(command);
  }
}
With that, I can get access to the history singleton in my client code and use it to undo and redo commands as much as I like:
  var history = new History();

  btnUp.press();
  btnUp.press();
  btnUp.press();

  history.undo();
  history.undo();
  history.redo();

  print("\nRobot is now at: ${robot.location}");
That does the trick as the robot code that responds to these commands moves three paces to the north (in response to three button-up presses), undoes two of those steps, then re-does one of the undoes:
$ ./bin/play_robot.dart                          
[pressed] Up
  I am moving Direction.NORTH
[pressed] Up
  I am moving Direction.NORTH
[pressed] Up
  I am moving Direction.NORTH
Undoing Instance of 'MoveNorthCommand'
  I am moving Direction.SOUTH
Undoing Instance of 'MoveNorthCommand'
  I am moving Direction.SOUTH
Re-doing Instance of 'MoveNorthCommand'
  I am moving Direction.NORTH

Robot is now at: 0, 2
For a grand total of 2 steps forward.

I am reasonably happy with the resulting code, but there is another benefit to making this change. I can now treat history as a command receiver in the command pattern:
  // Receivers
  var robot = new Robot();
  var camera = robot.camera;
  var history = new History();

  // ...
  var moveNorth = new MoveNorthCommand(robot),
    // ...
    undo = new UndoCommand(history),
    redo = new RedoCommand(history);

  // Invokers
  var btnUp = new Button("Up", moveNorth);
  // ...
  var btnUndo = new Button("Undo", undo);
  var btnRedo = new Button("Redo", redo);

  btnUp.press();
  btnUp.press();
  btnUp.press();

  btnUndo.press();
  btnUndo.press();
  btnRedo.press();

  print("\nRobot is now at: ${robot.location}");
The undo/redo commands only need to store the history instance so that they can call the appropriate action in response to a button press:
class UndoCommand implements Command {
  History history;
  UndoCommand(this.history);
  void call() { history.undo(); }
}
That history is itself a command now feels like a nice symmetry. In addition to the improved command implementation, I realize that I have an unfortunate habit of using static methods when writing exploratory code. I need to watch that or I am going to get myself intro trouble one of these days. But for today, I am safe.

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


Day #35

No comments:

Post a Comment