One of our biggest challenges as software developers is organizing our code so that it is easier to extend and maintain. The Command pattern helps us do that by encapsulating all the data required to perform an action into a single Command
object.
You might recognize the Command pattern because we use it all the time in our everyday lives. A good example is using a remote control device to turn on a television, switch channels, turn up the volume, and so on. Every one of these actions is encapsulated in the remote control device.
Something else to note about all of these actions is that they are reversible: you can turn on the TV, and you can also turn it off. Additionally, some of the actions must be done in order: you must turn on the TV before you can turn up the volume.
In this Java code challenge, you'll learn about the Command design pattern and see several examples of the pattern in practice. I will also discuss how the Command pattern implements two core principles of the SOLID model. The two principles are the single-responsibility principle, which states that a class should have only one job, and the open-closed principle, which states that objects or entities should be open for extension but closed for modification.
What is the Command pattern?
The Command pattern is one of the 23 design patterns introduced with the Gang of Four design patterns. Command is a behavioral design pattern, meaning that it aims to execute an action in a specific code pattern.
When it was first introduced, the Command pattern was sometimes explained as callbacks for Java. While it started out as an object-oriented design pattern, Java 8 introduced lambda expressions, allowing for an object-functional implementation of the Command pattern. This article includes an example using a lambda expression in the Command pattern.
As with all design patterns, it's very important to know when to apply the Command pattern, and when another pattern might be better. Using the wrong design pattern for a use case can make your code more complicated, not less.
The Command pattern in the JDK
We can find many examples of the Command pattern in the Java Development Kit, and in the Java ecosystem. One popular example is using the Runnable
functional interface with the Thread
class. Another is handling events with an ActionListener
. Let's explore both of these examples.
The Command pattern with Thread and Runnable
Runnable
is an interface that includes the run()
method. The following code snippet shows the run()
method's signature. As you can see, it is possible to pass a command in the run()
method:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Thread
is the most-used class that receives a Runnable
. Let's see how we can pass a command to the Thread
class:
Runnable command = () -> System.out.println("Executing command!");
Thread thread = new Thread(command); // Setting command
thread.start();
In this code, we implement the command behavior in the run()
method with a lambda expression. Instead of the lambda, we could use an anonymous inner class, which is an unnamed class that implements Runnable
and the run()
method. But that approach would make the code more verbose. Using the lambda is more concise and easier to read.
We then pass the command to the Thread
class. Finally, we execute the command by invoking the start()
method.
Here's the output we can expect from this code:
Executing command!
The Command pattern with ActionListener
Another good example in the JDK is the ActionListener
interface. I know it's an older interface, but it's suitable as an example.
In the following code, we create a JFrame
and a JButton
. We then set the action in the button by invoking the addActionListener()
method. In this case, we'll just change the text from "Click me" to "Clicked." Then, we'll add the button to the frame, and show the frame with the button:
JFrame frame = new JFrame();
JButton button = new JButton("Click Me");
button.addActionListener(e -> button.setText("Clicked!")); // Command implementation
frame.add(button);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Figure 1 shows the results of this code after the button is clicked.
Drive my motorcycle! The Command pattern in a Vehicle interface
Now that you've seen examples of the Command pattern in the JDK, let's create our own. First, take a look at the class diagram in Figure 2.
There are three parts to the diagram, which I'll explain.
Command
The foundation class for the Command pattern is the Command
interface. We use this interface anytime we want to execute or revert a command:
public interface Command {
void execute();
void revert();
}
Receiver
Next, we need to create the class that has the behavior to execute the command. We start with the Vehicle
interface, then create the Motorcycle
and Truck
classes to implement it:
public interface Vehicle {
void start();
void stop();
void accelerate();
}
public class Motorcycle implements Vehicle {
@Override
public void start() {
System.out.println("Starting motorcycle...");
}
@Override
public void stop() {
System.out.println("Stopping motorcycle...");
}
@Override
public void accelerate() {
System.out.println("Accelerating motorcycle...");
}
}
public class Truck implements Vehicle {
@Override
public void start() {
System.out.println("Starting truck...");
}
@Override
public void stop() {
System.out.println("Stopping truck...");
}
@Override
public void accelerate() {
System.out.println("Accelerating truck...");
}
@Override
public void decelerate() {
System.out.println("Decelerating truck...");
}
}
Also notice that the Vehicle
interface makes the code more flexible and easier to change: we could easily add another vehicle such as Car
that implements the Vehicle
interface. This part of the Command pattern is a great example of the open-closed SOLID principle. (Remember that this principle states that objects or entities should be extensible.)
Invoker
Now, we have the Motorcycle
and Truck
behavior but we need a class to execute it. In our case, this class will be the GhostRider
. GhostRider
will drive the Motorcycle
and Truck
classes.
GhostRider
receives the command in the constructor and invokes the execute()
method from the command into the takeAction()
and revertAction()
methods:
public class GhostRider {
Command command;
public GhostRider(Command command){
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void takeAction(){
command.execute();
}
public void revertAction() {
command.revert();
}
}
Implementing commands in the Command pattern
Now, let's create the StartMotorcycle
, AccelerateMotorcycle
, and StartAllVehicles
commands. Each command implements the Command
interface and receives Vehicle
in the constructor. Then, it invokes the method that corresponds to each command class from Vehicle
into the execute()
method:
public class StartMotorcycle implements Command {
Vehicle vehicle;
public StartMotorcycle(Vehicle vehicle) {
this.vehicle = vehicle;
}
public void execute() {
vehicle.start();
}
@Override
public void revert() {
vehicle.stop();
}
}
public class AccelerateMotorcycle implements Command {
Vehicle vehicle;
public AccelerateMotorcycle(Vehicle vehicle){
this.vehicle = vehicle;
}
public void execute() {
vehicle.accelerate();
}
@Override
public void revert() {
vehicle.decelerate();
}
}
import java.util.List;
public class StartAllVehicles implements Command {
List<Vehicle> vehicles;
public StartAllVehicles(List<Vehicle> vehicles) {
this.vehicles = vehicles;
}
public void execute() {
vehicles.forEach(vehicle -> vehicle.start());
}
@Override
public void revert() {
vehicles.forEach(vehicle -> vehicle.stop());
}
}
Run the commands
It's time to run our commands! For this, we first instantiate the Motorcycle
class that has the Command
behavior, then pass it into each Command
implementation.
Notice that we are also using the StartAllVehicles
command to start (and stop) multiple vehicles at once.
Then, we instantiate the GhostRider
class that will execute each command. Finally, we invoke the takeAction()
and revertAction()
methods:
public class RideVehicle {
public static void main(String[] args) {
Vehicle motorcycle = new Motorcycle();
StartMotorcycle startCommand = new StartMotorcycle(motorcycle);
GhostRider ghostRider = new GhostRider(startCommand);
ghostRider.takeAction();
AccelerateMotorcycle accelerateCommand = new AccelerateMotorcycle(motorcycle);
ghostRider.setCommand(accelerateCommand);
ghostRider.takeAction();
ghostRider.revertAction();
Vehicle truck = new Truck();
List<Vehicle> vehicles = List.of(motorcycle, truck);
StartAllVehicles startAllVehicles = new StartAllVehicles(vehicles);
startAllVehicles.execute();
startAllVehicles.revert();
}
}
Here is the output from this code:
Starting motorcycle...
Accelerating motorcycle...
Decelerating motorcycle...
Starting motorcycle...
Starting truck...
Stopping motorcycle...
Stopping truck…
When to use the Command pattern
A crucial rule for design patterns is to know when to use them. No matter how great a pattern is, implementing it for the wrong use case will make your code much worse. Here are some guidelines for using the Command pattern:
- You have multiple commands that should be implemented separately based on the SOLID principles of single-responsibility and open-closed design.
- You need to create reversible commands, such as adding and removing an item from a shopping cart.
- You need to be able to create logs whenever a command is executed. Each command in the Command pattern is encapsulated, so creating logs is easy.
- You need to be able to execute multiple commands at once. You can easily add a
Queue
,List
, orSet
into a command's implementation and execute them.
What to remember about the Command pattern
To summarize, remember the following about the Command pattern:
- It applies the SOLID principles of single-responsibility and open-closed design.
- It encapsulates and decouples the behavior of commands, which makes your code more extensible.
- It's used in the JDK with the
Thread
class andRunnable
andActionListener
interfaces. - It encapsulates the behavior of commands within a single
Command
implementation. - It lets you execute and revert single commands.
- It lets you execute and revert multiple commands together.