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 foundAnd 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
You could do something similar to a C++ template. Like the code I've created here
ReplyDeletehttps://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.
I was planning on doing something like that SimpleCommand, but ran out of time :D
DeleteThe 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 :)
Yes I know how vain you are :-P
DeleteLooking forward to it
Yes I know how vain you are :-P
DeleteLooking forward to it
At least not without mirrors
ReplyDelete