Thursday, December 10, 2015

Argument Passing in the Command Pattern

One of the things I appreciate in the command pattern example from the Gang of Four book is that it uses different kinds of receivers. One of the things that bugs me is how the example passes arguments from the client to the command.

The GoF example is a document editor menu. The create-new-document menu item points to a create-document command which is executed when the menu item is selected in the UI. To obtain the filename of the new document, the GoF example invokes an askUser() function which presumably pops up a dialog to await user input. I suppose that is not too horrible, but what if the command argument is already known?

To explore that, I switch from my previous light switch command example to a new take on the VelvetFogMachine—a jukebox-like device intended for playing and organizing songs of the late, great Mel Tormé. This Dart example uses a menu invoker, much like the invoker from the GoF example:

// Invoker
var menu = new Menu();

It defines two receivers, the VelvetFogMachine and Playlist instances used to find the ultimate Mel mix:

Like most jukeboxes, the velvet fog machine can play a song by selecting it from a list, then pressing the menu item associated with menuPlay. But how to get that song to the command?

My first instinct is to muck with noSuchMethod to obtain optional execution arguments via reflection. That seems likely to cause trouble. Instead, I start by defining am args property on the Command interface:

abstract class Command {
List args;
void call();
}

Then, in the invoker, I assign this property when invoking with arguments:

That works, but feels less than ideal. The client code is simple enough:

menu.call(menuPlay, ['It Had to Be You']);

But the args property just feels awkward.

Hunh. I don't know why I didn't try this first, but the Command interface can be defined to accept optional arguments when invoked:

abstract class Command {
void call([List args]);
}

I had worried that static typing would bite me when trying to define call() methods when some subclasses required parameters while others did not need them. Dart's optional arguments resolves the problem neatly. Command like "play" that require an argument can support them:

The Dart type analyzer balks if I specify a completely empty call() definition since it does not quite match the interface. But if I specify the argument with the underscore convention for ignoring arguments, I essentially declare a call() with no arguments. That's slightly hacky, but not so much that I am going to lose sleep over it.

I am fairly happy with this argument passing approach. Calling these commands:

./bin/jukebox.dart
Play It Had to Be You
--
Play 'Round Midnight
It Don't Mean A Thing (If It Ain't Got That Swing)
New York, New York
The Lady is a Tramp
--
==> [add] Blue Moon
Play 'Round Midnight
It Don't Mean A Thing (If It Ain't Got That Swing)
New York, New York
The Lady is a Tramp
Blue Moon
--
==> [remove] The Lady is a Tramp
Play 'Round Midnight
It Don't Mean A Thing (If It Ain't Got That Swing)
New York, New York
Blue Moon
--
==> [clear]
Play

This seems a good stopping point for tonight. Up tomorrow, I will explore the implications of undo in the command pattern when working with multiple kinds of receivers.