Command Design Pattern – Head First Approach

Alyaa TalaatAlyaa Talaat
5 min read

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.


0
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.