A compendious (and thunky!) introduction to the Command Pattern.
The command in the command pattern couldn’t be simpler: it is the combination of a thing and an action. It’s an object and a method to be invoked on that object. That’s it!
By itself, a command doesn’t count as a Pattern™. It’s just another object. What makes the pattern is the “how” and the “why” it gets used.
“How” command objects get used is via a set of similar objects. These invoker objects are usually (but are not limited to) UI elements—think buttons or menu items. And they need to be able to tell another object to do something.
The pattern arises from a natural desire for consistency. You don’t want one button to call a function while another invokes a command via a
call() method while a third uses a
do() method. That’s silly. The common sense desire for all buttons to invoke their commands in the same way is what gives rise to the pattern.
Consistency is not the sole benefit of the command pattern. Once you have a command object that can tell a series of objects to do something, it is a short distance to being able to tell those same objects to undo those actions. It also gets easier to combine actions into macro commands.
We start with a
Robot class, which is fairly simple. It takes some x-y coordinates, defaulting to zero-zero. We have a location getter to report on how we are doing and where our robot is at. And we have
move() methods to move the robot in various directions. That’s our robot.
In pattern terminology, that’s going to be our receiver. It will “receive” the action via commands.
Next up, we need buttons to test this out. It will store a name (Up, Down, Left, Right, etc.). It also needs to store a command. The
Button constructor requires and assigns those two instance variables. Finally, when the button is pressed, the command is called.
In pattern terminology, that’s going to be our invoker. It invokes the commands that tell objects to do something.
Down in the application code, we add a “Right” button. The command will send the
moveRight() action to the robot receiver. Similarly, when the “Left” button is pressed, we want the robot to move left. When we hit the “Up” button, the robot should move up and when we hit the “Down” button, the robot should go… down.
All right, let’s play a little. We press the right button, which changes the robot’s x-y location to.. (1,0). If we press the button twice, the robot’s x-y location changes to (2,0). Then, we press left once, then up, and down. The net result of moving both up and down is no change in the Y position. By moving right twice and then back left once, we change the X position by one. So our final location should be 1,0… which it is.
So everything seems to be working. Great!
You’ll notice here that our “command” is just a function. The buttons get functions that, when called, send
move() actions to the robot. Since they send actions to receivers, these functions very much count as official commands. And, since each function is called the same way (as a function), this counts as a command… pattern. Just because it’s simple doesn’t mean it’s not worthy of the terminology.
Speaking of simple, if a language supports tear-off methods, these commands get simpler and prettier. A tear-off method is a language feature that allows the assignment of a method complete with its associated object. That is, if I assign the
robot.moveUp and then call the
move variable, it will tell the robot to move up! That won’t work in non-compendious languages, but it does in Dart.
So we can replace the function calls with tear-offs like so…
…and our command pattern code still works and it much prettier for it.
That’s the compendious and quite thunky introduction to the command pattern. We’re definitely not done yet. In the complete command series, we’ll look at the implications and power of the command pattern including undo, using multiple receivers, macros, and dynamic commands.
Oh, it’s gonna be Thunky!