08 Jan 2025

Command Pattern

Overview

The Command Pattern is a behavioral design pattern that encapsulates a request as an object, thereby allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations.

This pattern decouples the sender (invoker) of a request from its receiver by introducing an intermediary object, the command, which encapsulates the details of the request.

Key Participants

  1. Command

  • Declares the interface for executing an operation (execute()).

  1. ConcreteCommand

  • Implements the Command interface by binding a receiver to an action.

  • Calls the receiver’s action in its execute() method.

  1. Receiver

  • Performs the actual work when the execute() method is called on the command.

  1. Invoker

  • Stores the command and triggers its execution.

  • Maintains a history of commands for undo/redo functionality.

  1. Client

  • Creates specific commands and assigns them to the invoker.

Implementation in Code

Example: Smart Home Automation

// Command Interface
interface Command {
    void execute();
    void undo();  // Optional, for undoable operations
}

// Receiver
class Light {
    public void turnOn() {
        System.out.println("The light is ON");
    }

    public void turnOff() {
        System.out.println("The light is OFF");
    }
}

// ConcreteCommand
class LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOn();
    }

    @Override
    public void undo() {
        light.turnOff();
    }
}

class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOff();
    }

    @Override
    public void undo() {
        light.turnOn();
    }
}

// Invoker
class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }

    public void pressUndo() {
        command.undo();
    }
}

// Client
public class CommandPatternDemo {
    public static void main(String[] args) {
        // Receiver
        Light livingRoomLight = new Light();

        // ConcreteCommands
        Command lightOn = new LightOnCommand(livingRoomLight);
        Command lightOff = new LightOffCommand(livingRoomLight);

        // Invoker
        RemoteControl remote = new RemoteControl();

        // Turn the light ON
        remote.setCommand(lightOn);
        remote.pressButton();

        // Undo the operation
        remote.pressUndo();

        // Turn the light OFF
        remote.setCommand(lightOff);
        remote.pressButton();

        // Undo the operation
        remote.pressUndo();
    }
}

Key Methods

  1. execute(): Executes the encapsulated request.

  1. undo() (Optional): Reverts the operation, enabling undo functionality.

Advantages

  1. Decoupling: Decouples the sender of the request (Invoker) from its receiver.

  1. Extensibility: New commands can be added without changing existing code.

  1. Undo/Redo: Enables history of commands for undo/redo functionality.

  1. Macro Commands: Allows grouping multiple commands into a single macro command.

Disadvantages

  1. Command Explosion: A large number of commands may be required for complex systems.

  1. Overhead: Adds extra layers of abstraction, potentially increasing complexity.

Real-World Examples

  1. GUI Buttons: Each button click triggers a specific command (e.g., save, delete, print).

  1. Smart Home Devices: Commands to control lights, fans, or thermostats via a central controller.

  1. Undo/Redo in Applications: Text editors and drawing applications where user actions can be reversed.

When to Use the Command Pattern?

  • To encapsulate requests as objects, allowing parameterization and delayed execution.

  • To implement undo/redo functionality.

  • To decouple sender and receiver objects.

  • To create a history of operations.

← Back to Library