I fancied myself done with the command pattern in Dart. Anders Holmgren had other ideas. In the comments on last night's "final" command pattern article, he had a number of insightful suggestions that I have yet to consider.
So consider I do, tonight.
First up, I had not been constraining my function or object commands. Just about everywhere I use commands, I allow it to be a generic
Function
:class Button { String name; Function command; Button(this.name, this.command); void press() { print("[pressed] $name"); command.call(); History.add(command); } }It should be noted here that any class that supports a
call()
method is a Function
. So command
here can be a function or function-like object.As can be seen in the
press()
method, the function or object never takes any arguments. Furthermore, it never returns a value, so its return type should be void. Although the following Button
would result in a run-time error, the Dart analyzer thinks that it is perfectly OK:new Button("Test", (String arg) => "$arg $arg $arg");The anonymous function should neither accept an argument nor should it return a value as it does with the hash rocket symbol.
Thankfully, Anders has me covered here. I should declare a
typedef
that describes my command interfaces:typedef void _Command();Anything with a type of
_Command
will now be required by the analyzer to return nothing and accept no arguments.I replace all
Function
types with _Command
, including in my Button
invoker:class Button { String name; _Command command; Button(this.name, this.command); void press() { /* ... */ } }Now the analyzer complains about my "Test" button:
[warning] The argument type '(String) → String' cannot be assigned to the parameter type '_Command'So typedefs are a great suggestion.
What I would like to do next is replace the
Function
in the Command
class declaration:abstract class Command implements Function { void call(); }I would like to explicitly tie my
Command
class with the void-no-argument _Command
typedef:abstract class Command implements _Command { void call(); }That will not work however because:
[error] Classes can only implement other classesI am not entirely out of luck, however. Even though there is no way to explicitly tie
Command
to my typdef, it is already implicitly associated. Any function-like thing that accepts no arguments and returns void
is a _Command
typedef. Thus:abstract class Command implements Function { /* ... */ } abstract class UndoableCommand implements Command { /* ... */ } class MoveNorthCommand implements UndoableCommand { // ... void call() { robot.move(Direction.NORTH); } } main() { // ... var moveNorth = new MoveNorthCommand(robot); print("MoveNorthCommand is a _Command: ${moveNorth is _Command}"); }Results in:
MoveNorthCommand is a _Command: trueSo Dart already knows that anything implementing
Command
—or even declaring a void-no-arg call()
—is a _Command
. In other words, there is no way to explicitly constrain a function class to a typedef interface... other than using a clever trick that Anders employs in the
UndoCommand
declaration. Even though I cannot extend or implement a typedef in a class declaration, I can extend a type in a generic:abstract class UndoableCommand<T extends _Command> implements Command { void call(); T get undoCommand; }The type of
undoCommand
is now restricted (upper-limitted?) to be a no-arg-void-return _Command
. I remain unsure about that, however as it seems a bit... obscure. Why should it work to extend a generic type when extending the source class with the same thing does not? Regardless, it is certainly a nifty tool to keep in my toolbelt.Much thanks to Anders for his pointers. I think I still have another point or two that I still need to cover. Tomorrow.
Day #45
No comments:
Post a Comment