Introduction
The command pattern is most commonly used in scenarios where commands or requests are made. Commands are encapsulated as Objects
which simplifies maintenance and maximizes Object
extension.
Example Problem
As a side project I wrote an Android application which acts as a remote control for computers in my home. The application works by making an SSH
connection and executing commands to trigger functions such as “play music” and “stop music”. I started writing the application in a POC (proof of concept) style. Most of the commands were strings being passed around to several methods. As more functionality and devices were added technical debt grew. Adding new commands and maintaining existing ones became tedious.
This example contains the following structure:
Command
: Interface that represents a commandConcrete Command
: Represents a specific command to be made such as “play music” or “stop music”Invoker
: Holds commands and executes them as needed (the toggle button listener for this example).Client
: The ApplicationReceiver
: A class which knows how to execute the command (Computer
for this example).
This post describes the basics of the command pattern without showing the internal details of the Receiver (Computer
) class. Just know that Computer
knows how to playMusic()
or stopMusic()
when it’s told to. Here’s the implementation of the command pattern:
Command Interface
Here’s the Command
interface. It has an execute()
method. All commands will implement this interface.
public interface Command { public void execute(); } |
Concrete Command Object
Let’s write a couple concrete command Objects
for our “play music” and “stop music” commands. Concrete command objects take the Receiver
as a constructor argument.
public class PlayMusicCommand implements Command { Computer computer; // Receiver public PlayMusicCommand(Computer computer) { this.computer = computer; } @Override public void execute() { computer.playMusic(); } } public class StopMusicCommand implements Command { Computer computer; public PlayMusicCommand(Computer computer) { this.computer = computer; } @Override public void execute() { computer.stopMusic(); } } |
Invoker – Toggle Button Listener
The toggle button listener listens for clicks / touches and knows if it’s in an on
or off
state based on the isChecked
boolean. The invoker doesn’t care about the details of the command it only cares about invoking execute()
.
public class ToggleButtonListener implements CompoundButton.OnCheckedChangeListener { private Command onCommand; private Command offCommand; public ToggleButtonListener(Command onCommand, Command offCommand) { this.onCommand = onCommand; this.offCommand = offCommand; } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) offCommand.execute(); else onCommand.execute(); } } |
Client (Application)
Here’s a application with a ToggleButton
that plays and stops music (Toggle Buttons have an on and off state). When a user turns the button to an on
state music should start playing (on the living room computer).
public class MainActivity extends AppCompatActivity { // Receiver Computer livingRoomComputer = new LivingRoomComputer(); // Command(s) Command playMusicCommand = new PlayMusicCommand(livingRoomComputer); Command stopMusicCommand = new StopMusicCommand(livingRoomComputer); // Invoker ToggleButtonListener playMusicToggleButtonListener = new ToggleButtonListener(playMusicCommand, stopMusicCommand); // Client public void onCreate(Bundle savedInstanceState) { playMusicToggleButton.setOnCheckChangedListener(playMusicToggleButtonListener); } } |
Summary
While this example only covers on
and off
functionality it still shows the power of the command pattern. When new devices (Receivers
) or commands are added the client can just write new implementations and extend functionality as needed.
Initially when I learned about this pattern my first concern was how many concrete Command
implementations a large application might end up with. If you’re using Java 8+ you can pretty the command pattern using Lambda
expressions. For example instead of the client writing concrete implementations of PlayMusicCommand
and StopMusicCommand
they can just inline it. This is allowed because Command
has a single method execute()
. The compiler knows to invoke execute()
it just needs to know what the method body will be.
ToggleButtonListener playMusicToggleButtonListener = new ToggleButtonListener( () -> livingRoomComputer.playMusic(), // on command () -> livingRoomComputer.stopMusic()); // off command |