My daughter loves BB-8 (yes, I'm that guy). She can't walk into my office without asking for her friend "BB." I think she could spend hours chasing him around.
At some point while mucking around with it, I realized that a robot controlled by a Dart application would a good example of the command pattern. In fact, I think it might be better example for Design Patterns in Dart than the Velvet Fog Machine that I have been using. Hard as it may be for me to accept, there are more folks familiar with robots than with Mel Tormé.
So tonight, I write an implementation of the command pattern for a robot. The command pattern has three components: the invoker, the receiver, and the command. The invoker in this robot example will be a button within an app. There will be multiple buttons for moving in different directions, executing macros, and capturing video. For the command receivers, I will stick with the robot and a camera. The commands will link the receiver with an action.
I find the Gang of Four book confusing when it includes an interface and the client code as "participants" in the pattern. There are three specific objects in this pattern, but the inclusion of these other two consistently makes me think that I am missing a participant. I'm sure it is just semantics, but an interface does not actively do anything in the pattern—it describes the methods that concrete commands must have.
Every application has a client, so it is hard to think of that as a participant—it is the context of the application, not a participant, darn it. That said, the client does participate in this pattern—it is responsible for associating commands with receivers. The robot and its camera are the receivers in this case:
// Client main() { // Receivers var robot = new Robot(); var camera = robot.camera; // ... }The commands could be a variety of directional movements and camera actions:
// Concrete command instances
var moveNorth = new MoveNorthCommand(robot),
moveSouth = new MoveSouthCommand(robot),
moveEast = new MoveEastCommand(robot),
moveWest = new MoveWestCommand(robot),
startRecording = new StartRecordingCommand(camera),
stopRecording = new StopRecordingCommand(camera);
With that, the commands are linked with specific instances of the receivers.There is nothing special about the
Robot
and Camera
classes. Any class can be a receiver in this pattern and that is what they do here.The command classes need to support a
call()
as the mechanism for executing concrete commands:abstract class Command { void call(); }For this particular case, the commands need to associate the receiver with the action:
class MoveNorthCommand implements Command { Robot robot; MoveNorthCommand(this.robot); void call() { robot.move(Direction.NORTH); } }The
robot
instance is assigned by client code. When this command is called, that robot
instance moves north. The other direction command are identical save for the directions.All that remains is the invoker. In this case, buttons in the UI will invoke commands. The buttons have names and are assigned commands, so the class definition is:
class Button { String name; Command command; Button(this.name, this.command); void press() { print("[pressed] $name"); command.call(); } }Back in the client, these are instantiated as:
// Invokers
var btnUp = new Button("Up", moveNorth);
// ...
var btnRecord = new Button("Record", startRecording);
var btnStopRecord = new Button("Stop Recording", stopRecording);
That is all there is. I can press a bunch of buttons: btnUp.press();
btnUp.press();
btnUp.press();
btnRight.press();
btnRight.press();
btnRight.press();
btnLeft.press();
btnDown.press();
btnRecord.press();
btnStopRecord.press();
print("\nRobot is now at: ${robot.location}"
And, thanks to a few judicious print()
statements, I see:./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 [pressed] Down I am moving Direction.EAST [pressed] Down I am moving Direction.EAST [pressed] Down I am moving Direction.EAST [pressed] Left I am moving Direction.WEST [pressed] Down I am moving Direction.SOUTH [pressed] Record Capturing video record [pressed] Stop Recording Video record complete Robot is now at: 2, 2So it works! And now that I see it... I am not quite as sure that I ought to use it instead of the Velvet Fog Machine example.
The commands feel too literal. They are quite explicit commands issued to a robot. Although that is a legitimate use of the command pattern, it has an artificial taste. I would prefer the examples in the book to be a bit closer to real-life examples. Sure, this kind of thing can happen, but the more interesting uses of the pattern occur when the commands are not obvious from the outset.
I will sleep on that. For now, here is the DartPad for further experimenting with the command and robots: https://dartpad.dartlang.org/3b2ac3f421db89c2b56a.
Day #33
No comments:
Post a Comment