• Shift Elevate
  • Posts
  • Command Pattern: Encapsulate Requests as Objects for Flexible Control

Command Pattern: Encapsulate Requests as Objects for Flexible Control

The Pain of Tightly Coupled Control Systems

Picture this: You're building a smart home automation system with lights and ceiling fans. Your initial approach seems straightforward: directly call methods on each device from your control interface.

Then your product team drops a bombshell: "We need to add voice control, mobile app control, and schedule based automation. Also, users want to undo their last action and create custom macros like a 'Movie Time' scene that turns off lights and turns on the ceiling fan."

Suddenly, you're not just adding new control methods: you're creating a tangled web of dependencies, violating the Single Responsibility Principle, and building a maintenance nightmare that makes every new feature a breaking change. Sound familiar?

The Command pattern solves this headache by encapsulating requests as objects, allowing you to parameterize clients with different requests, queue operations, and support undoable operations.

Understanding the Command Pattern

The Command pattern turns requests into objects, making it easy to queue operations, support undo functionality, and create flexible control systems.

Think of it as a universal remote control for your smart home: each button press (command) is a self contained instruction that can be executed, undone, or combined with others without knowing the internal details of the devices.

This pattern promotes loose coupling by separating the object that invokes the operation from the object that knows how to perform it.

Command Pattern Components

Core Components

  • Command Interface: The common contract that all commands must honour.

  • Receiver Classes: The objects that know how to perform the actual work.

  • Concrete Commands: The actual implementations that operate on receivers.

  • Macro Command: The composite command that executes multiple commands.

  • Invoker: The object that knows how to execute commands.

Complete Java Implementation

Let's build a smart home automation system that demonstrates the Command pattern's elegance. Here's how the magic happens:

Command Interface

public interface Command {
    void execute();
    void undo();
    void redo();
}

Receiver Classes

// Light class represents a smart light device (Receiver in Command pattern)
public class Light {
    private String location;
    private boolean isOn = false;
    
    public Light(String location) {
        this.location = location;
    }
    
    public void turnOn() {
        isOn = true;
        System.out.println(location + " light is now ON");
    }
    
    public void turnOff() {
        isOn = false;
        System.out.println(location + " light is now OFF");
    }
    
    public boolean isOn() { 
        return isOn; 
    }
}

// Fan class represents a ceiling fan device (Receiver in Command pattern)
public class Fan {
    private String location;
    private int speed = 0; // 0 = off, 1-3 = speed levels
    
    public Fan(String location) {
        this.location = location;
    }
    
    public void setSpeed(int speed) {
        this.speed = Math.max(0, Math.min(3, speed));
        if (this.speed == 0) {
            System.out.println(location + " fan is OFF");
        } else {
            System.out.println(location + " fan speed set to " + this.speed);
        }
    }
    
    public int getSpeed() { 
        return speed; 
    }
}

Concrete Commands

// LightOnCommand encapsulates the "turn light on" operation
public class LightOnCommand implements Command {
    private Light light;
    private boolean wasOn;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        wasOn = light.isOn();
        light.turnOn();
    }
    
    @Override
    public void undo() {
        if (!wasOn) {
            light.turnOff();
        }
    }
    
    @Override
    public void redo() {
        light.turnOn();
    }
}

// LightOffCommand encapsulates the "turn light off" operation
public class LightOffCommand implements Command {
    private Light light;
    private boolean wasOn;
    
    public LightOffCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        wasOn = light.isOn();
        light.turnOff();
    }
    
    @Override
    public void undo() {
        if (wasOn) {
            light.turnOn();
        }
    }
    
    @Override
    public void redo() {
        light.turnOff();
    }
}

// FanCommand encapsulates fan speed operations
public class FanCommand implements Command {
    private Fan fan;
    private int previousSpeed;
    private int newSpeed;
    
    public FanCommand(Fan fan, int newSpeed) {
        this.fan = fan;
        this.newSpeed = newSpeed;
    }
    
    @Override
    public void execute() {
        previousSpeed = fan.getSpeed();
        fan.setSpeed(newSpeed);
    }
    
    @Override
    public void undo() {
        fan.setSpeed(previousSpeed);
    }
    
    @Override
    public void redo() {
        fan.setSpeed(newSpeed);
    }
}

Macro Command

// MacroCommand combines multiple commands into a single executable unit
public class MacroCommand implements Command {
    private List<Command> commands;
    
    public MacroCommand(List<Command> commands) {
        this.commands = new ArrayList<>(commands);
    }
    
    @Override
    public void execute() {
        System.out.println("Executing macro command...");
        for (Command command : commands) {
            command.execute();
        }
    }
    
    @Override
    public void undo() {
        System.out.println("Undoing macro command...");
        // Undo in reverse order
        for (int i = commands.size() - 1; i >= 0; i--) {
            commands.get(i).undo();
        }
    }
    
    @Override
    public void redo() {
        System.out.println("Redoing macro command...");
        for (Command command : commands) {
            command.redo();
        }
    }
}

Invoker

// RemoteControl acts as the Invoker, managing command execution and history
public class RemoteControl {
    private Map<String, Command> commands;
    private Command lastCommand;
    
    public RemoteControl() {
        commands = new HashMap<>();
    }
    
    public void setCommand(String button, Command command) {
        commands.put(button, command);
    }
    
    public void pressButton(String button) {
        Command command = commands.get(button);
        if (command != null) {
            command.execute();
            lastCommand = command; // Store for undo/redo
        } else {
            System.out.println("No command assigned to button: " + button);
        }
    }
    
    public void undoLastCommand() {
        if (lastCommand != null) {
            System.out.println("Undoing last command...");
            lastCommand.undo();
        } else {
            System.out.println("No commands to undo");
        }
    }
    
    public void redoLastCommand() {
        if (lastCommand != null) {
            System.out.println("Redoing last command...");
            lastCommand.redo();
        } else {
            System.out.println("No commands to redo");
        }
    }
}

Client Code

// Client code demonstrates the Command pattern in action
public class SmartHomeLauncher {
    public static void main(String[] args) {
        // Create devices (receivers)
        Light livingRoomLight = new Light("Living Room");
        Fan ceilingFan = new Fan("Living Room");
        
        // Create commands
        Command lightOn = new LightOnCommand(livingRoomLight);
        Command lightOff = new LightOffCommand(livingRoomLight);
        Command fanHigh = new FanCommand(ceilingFan, 3);
        Command fanOff = new FanCommand(ceilingFan, 0);
        
        // Create macro command for "Movie Time" scenario
        List<Command> movieTimeCommands = Arrays.asList(lightOff, fanHigh);
        Command movieTimeMacro = new MacroCommand(movieTimeCommands);
        
        // Create remote control (invoker)
        RemoteControl remote = new RemoteControl();
        remote.setCommand("1", lightOn);
        remote.setCommand("2", lightOff);
        remote.setCommand("3", fanHigh);
        remote.setCommand("4", fanOff);
        remote.setCommand("5", movieTimeMacro);
        
        // Demonstrate the system
        System.out.println("=== Smart Home Demo ===\n");
        
        // Test individual commands
        System.out.println("1. Turn on light:");
        remote.pressButton("1");
        
        System.out.println("\n2. Turn on fan:");
        remote.pressButton("3");
        
        // Test undo functionality
        System.out.println("\n3. Undo last command:");
        remote.undoLastCommand();
        
        // Test redo functionality
        System.out.println("\n4. Redo last command:");
        remote.redoLastCommand();
        
        // Test macro command
        System.out.println("\n5. Execute 'Movie Time' macro:");
        remote.pressButton("5");
        
        // Test undo on macro
        System.out.println("\n6. Undo macro command:");
        remote.undoLastCommand();
    }
}

Expected Output

=== Smart Home Demo ===

1. Turn on light:
Living Room light is now ON

2. Turn on fan:
Living Room fan speed set to 3

3. Undo last command:
Undoing last command...
Living Room fan is OFF

4. Redo last command:
Redoing last command...
Living Room fan speed set to 3

5. Execute 'Movie Time' macro:
Executing macro command...
Living Room light is now OFF
Living Room fan speed set to 3

6. Undo macro command:
Undoing last command...
Undoing macro command...
Living Room fan is OFF
Living Room light is now ON

🚀 Get the Complete Implementation

The full code with advanced command operations and macro functionality is available in our Design Patterns Repository.

# Clone and run the complete demo
git clone https://github.com/shift-elevate/design-patterns.git
cd design-patterns
mvn test -Dtest=CommandPatternTest

Real World Applications

The Command pattern is extensively used in real world applications across various domains:

1. Text Editors & IDEs

Modern text editors and IDEs use the Command pattern extensively for undo/redo functionality, command history, macro recording and playback, and plugin systems. Each user action becomes a command object that can be executed, undone, and replayed.

2. Game Development

Game engines use the Command pattern for input handling systems, replay functionality, AI behaviour trees, and achievement systems. This allows for features like game replay, undo moves, and complex AI decision making.

3. Financial Systems

Banking and financial applications use the Command pattern for transaction processing, audit trails, rollback mechanisms, and batch operations. Each financial transaction is encapsulated as a command that can be executed, logged, and potentially rolled back.

// Financial transaction system using Command pattern
public class TransactionCommand implements Command {
    private Account account;
    private double amount;
    private TransactionType type;
    private double previousBalance;
    
    public TransactionCommand(Account account, double amount, TransactionType type) {
        this.account = account;
        this.amount = amount;
        this.type = type;
    }
    
    @Override
    public void execute() {
        previousBalance = account.getBalance();
        if (type == TransactionType.DEPOSIT) {
            account.deposit(amount);
        } else if (type == TransactionType.WITHDRAWAL) {
            account.withdraw(amount);
        }
        System.out.println("Transaction executed: " + type + " " + amount);
    }
    
    @Override
    public void undo() {
        account.setBalance(previousBalance);
        System.out.println("Transaction undone: " + type + " " + amount);
    }
    
    @Override
    public void redo() {
        // Redo restores the transaction state after undo
        execute();
    }
}

When to Use the Command Pattern

Ideal Scenarios:

  • You need to parameterize objects with operations.

  • You want to queue operations, schedule their execution, or execute them remotely.

  • You need to support undoable operations.

  • You want to structure a system around high level operations built on primitive operations.

  • You need to support logging or auditing of operations.

Skip It When:

  • The operations are simple and don't need the flexibility of commands.

  • You're dealing with a small, simple system that won't grow.

  • Performance is critical and the overhead of command objects is too high.

Next Steps: Apply Command Pattern in Your Project

Ready to implement the Command pattern in your own projects? Here's a structured approach to get you started:

  1. Identify Control Operations: Look for areas where you need flexible control mechanisms, undo functionality, or operation queuing.

  2. Define Command Interface: Create a common interface for all operations with execute() and undo() methods.

  3. Implement Concrete Commands: Build command objects that encapsulate specific operations and their receivers.

  4. Create Invoker Classes: Build classes that can execute, queue, and manage command objects.

  5. Add Undo Functionality: Implement command history and undo mechanisms for better user experience.

The Command pattern transforms rigid control systems into flexible, extensible architectures. By encapsulating requests as objects, you build systems that adapt to change without breaking what already works.

Found this helpful? Share it with a colleague who's struggling with tightly coupled control systems. Have questions about implementing Command pattern in your specific use case? Email us directly, we read every message and the best questions become future newsletter topics.