Command Design Pattern – Head First Approach
The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all the information needed to execute the request. This allows for parameterizing clients with operations, queueing requests, and supporting undoable operations.
What is the Command Pattern?
Definition: The Command Pattern encapsulates a request as an object, allowing for the parameterization of clients with different requests, queueing of requests, and logging or undoing operations.
The Command Pattern decouples the object that sends a request from the object that receives it, enabling flexible command processing in a system.
Problem Scenario
Imagine you are developing a remote control system for a home automation setup. Your remote control needs to interact with various devices, such as lights, fans, and stereos, each with specific operations like turning on, off, increasing volume, etc. With multiple devices and different commands, handling all requests directly can quickly become complex. You need a way to encapsulate these actions as objects and then trigger them easily.
Using the Command Pattern
Let’s see how to implement the Command Pattern using a remote control example from the Head First Design Patterns book.
Step 1: Define the Command Interface
The command interface defines a common way to execute commands.
public interface Command {
public void execute();
}
Step 2: Create Concrete Command Classes
Each command class encapsulates a specific action. Here are a couple of concrete commands for controlling a light.
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
}
Step 3: Create Receiver Classes
The receiver class performs the actual operations. In this case, it’s the Light
class.
public class Light {
public void on() {
System.out.println("The light is on");
}
public void off() {
System.out.println("The light is off");
}
}
Step 4: Implement the Invoker
The RemoteControl
is the invoker that will trigger the commands.
public class SimpleRemoteControl {
Command slot;
public SimpleRemoteControl() {}
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute();
}
}
Step 5: Client Code
The client will create the necessary command objects, associate them with the invoker, and trigger the operations.
public class RemoteControlTest {
public static void main(String[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
LightOffCommand lightOff = new LightOffCommand(light);
// Turn the light on
remote.setCommand(lightOn);
remote.buttonWasPressed();
// Turn the light off
remote.setCommand(lightOff);
remote.buttonWasPressed();
}
}
Output:
The light is on
The light is off
In this example, we encapsulate light operations as command objects (LightOnCommand
and LightOffCommand
). The SimpleRemoteControl
acts as the invoker, calling the execute()
method on the command objects when a button is pressed.
Expanding the Command Pattern
Adding More Devices
You can easily add more devices and commands by creating new classes that implement the Command
interface. For example, here’s a command for a stereo system.
public class StereoOnCommand implements Command {
Stereo stereo;
public StereoOnCommand(Stereo stereo) {
this.stereo = stereo;
}
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
With this command, the Stereo
class might have methods like on()
, setCD()
, and setVolume()
, all of which are encapsulated in the StereoOnCommand
object.
Adding Undo Functionality
One of the great benefits of the Command Pattern is the ability to add undo functionality. To add an undo feature, you would need to add an undo()
method to your Command
interface and implement it in your concrete commands.
public interface Command {
public void execute();
public void undo();
}
Here’s an example implementation for the LightOnCommand
.
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
public void undo() {
light.off();
}
}
By implementing the undo()
method, the last executed command can be reversed, providing an easy way to revert actions.
Remote Control with Multiple Slots
To expand our remote control, we can introduce multiple command slots for different buttons.
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
// Initialize with NoCommand objects
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPressed(int slot) {
onCommands[slot].execute();
}
public void offButtonWasPressed(int slot) {
offCommands[slot].execute();
}
}
This implementation allows the remote control to manage commands for multiple devices, with each slot corresponding to a different device or function.
When to Use the Command Pattern
Queueing operations: If you need to queue, delay, or log operations.
Undo/Redo functionality: When you need to provide undo or redo features in your application.
Decoupling invokers from receivers: The Command Pattern allows you to decouple the object that invokes an operation from the object that performs it, adding flexibility to your design.
Parameterizing objects with actions: If you want to pass commands as method arguments or store them for later execution.
Bullet Points
The Command Pattern encapsulates a request as an object, allowing for flexible command execution.
It’s useful for implementing undo/redo functionality, queuing operations, and decoupling invokers from receivers.
Commands can easily be extended to support more devices or functionality.
It provides a unified interface for executing and undoing actions.
Conclusion
The Command Pattern is a powerful tool for decoupling requests from their execution. It allows for flexible command handling, such as adding undo functionality, supporting multiple devices, or queueing operations. By encapsulating actions as objects, the pattern provides a scalable way to manage complex systems with various commands.
Subscribe to my newsletter
Read articles from Alyaa Talaat directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Alyaa Talaat
Alyaa Talaat
As a continuous learner, I’m always exploring new technologies and best practices to enhance my skills in software development. I enjoy tackling complex coding challenges, whether it's optimizing performance, implementing new features, or debugging intricate issues.