AlgoMaster Logo

Command Design Pattern

Ashish

Ashish Pratap Singh

6 min read

The Command Design Pattern is a behavioral pattern that turns a request into a standalone object, allowing you to parameterize actions, queue them, log them, or support undoable operations — all while decoupling the sender from the receiver.

It’s particularly useful in situations where:

  • You want to encapsulate operations as objects.
  • You need to queue, delay, or log requests.
  • You want to support undo/redo functionality.
  • You want to decouple the object that invokes an operation from the one that knows how to perform it.

Let’s walk through a real-world example to see how we can apply the Command Pattern to decouple invokers from executors, and build a more flexible, extensible, and testable command execution framework.

1. The Problem: The Tightly Coupled Smart Home Controller

Imagine you're building a "Smart Home Hub" application.

This hub needs to control various devices:

  • 💡 Smart lights
  • 🌡️ Thermostats
  • 🔒 Security systems
  • 🔊 Smart speakers
  • 🚪 Garage doors

The hub should be able to send commands like:

  • light.on()light.off()
  • thermostat.setTemperature(22)
  • speaker.playMusic()

Naive Implementation: One Controller to Rule Them All

You might start with a simple class like this:

Light

Thermostat

Controller tightly coupled to devices

Example Usage

Why This Design Fails as the System Grows

This simple controller works for now, but quickly falls apart as complexity increases.

1. Tight Coupling

  • The SmartHomeControllerV1 class is directly tied to every device and their specific method names.
  • You can’t reuse or generalize actions — every new device requires a new method in the controller.

2. Poor Scalability

Adding a new device (e.g., SprinklerGarageDoorSmartSpeaker) means:

  • Adding new fields to the controller
  • Writing more methods for each action
  • Increasing the size and complexity of one giant class

Soon, your controller becomes a bloated monolith.

3. No Undo/Redo Support

There's no way to reverse a command. Want to implement an “undo last action” feature? You’d need to:

  • Track device states manually
  • Write custom undo logic for every method
  • Add a large switch/if-else block somewhere to figure out what action to reverse

It’s fragile, repetitive, and error-prone.

4. No Scheduling or Queuing

Imagine a user sets a rule like:

"Turn on the lights at 7:00 PM"

You can’t queue up what to do, because actions are hardcoded into method calls — not represented as standalone objects.

5. No Generic Logging

  • Want to log every action taken?
  • Or store a history of operations to replay them later?

Not possible without duplicating logging code inside each method.

6. Difficult UI Mapping

  • Suppose you're building a remote control with programmable buttons.
  • Or a mobile app with customizable scenes like "Movie Night" or "Leave Home".
  • Each UI button would need custom logic to know which device to call and how — tightly coupled, hard to scale, and impossible to reuse.

What We Really Need

We need to treat each command (e.g., “turn on light”, “set thermostat to 22°C”) as a standalone object — something that encapsulates:

  • What to do
  • Which device it affects
  • How to execute it
  • (Optionally) How to undo it

This way, the controller, remote, or scheduler doesn’t care how a command works — it just knows which command to execute.

This is exactly what the Command Design Pattern enables.

2. What is the Command Pattern

The Command Design Pattern is a behavioral pattern that turns a request into a standalone object, allowing you to:

  • Parameterize actions
  • Queue or log operations
  • Support undo/redo
  • Decouple the invoker of an operation from the receiver that performs it

Instead of calling methods directly on device classes, you encapsulate each request (like light.on() or thermostat.setTemperature(22)) as a Command object.

Class Diagram

1. Command (Interface)

  • Defines a standard interface for executing operations — typically an execute() method.
  • May also declare an undo() method if undo functionality is required.
  • Serves as the abstraction that all concrete commands will implement.

2. ConcreteCommand

  • Implements the Command interface.
  • Maintains a reference to the Receiver, and implements execute() by delegating to the receiver’s method(s).
  • May also capture the necessary state to support undo().

3. Receiver

  • The object that performs the actual work.
  • It contains the business logic that will be triggered by a command.
  • The receiver is not aware of the command or the invoker — it simply exposes the operations.

4. Invoker

  • Responsible for initiating command execution.
  • It holds a reference to a Command object and calls execute() when an action (e.g., button press) occurs.
  • The invoker does not know the specifics of the command or the receiver — it interacts only with the Command interface.

5. Client

  • Creates instances of ConcreteCommand, associating each with the appropriate Receiver.
  • Configures the Invoker with the command(s) to execute.
  • The client decides what actions to perform and sets everything up — but does not execute the commands itself.

How It Works

  • Each Command implements a standard interface like execute() (and optionally undo())
  • The Invoker (e.g., remote control, scheduler, UI) simply calls command.execute()
  • The Receiver (e.g., LightThermostat) performs the actual operation when the command is executed

This separation allows for highly flexible, decoupled, and extensible designs.

3. Implementing Command Pattern

Let’s refactor our Smart Home Controller to use the Command Pattern with support for undoable actions. We'll encapsulate each action as a command, decouple the invoker from the logic, and allow undoing previous actions.

1. Define the Command Interface

All commands must implement execute() and undo().

2. Define the Receivers (Devices)

These are the actual smart home devices that perform the actions.

💡 Light

🌡️ Thermostat

3. Implement Concrete Command Classes

Each command encapsulates a specific action and knows how to undo it.

LightOnCommand

LightOffCommand

SetTemperatureCommand

4. Create the Invoker (SmartButton) with Undo Support

5. Client Code – Using the Command System

Output

What We Achieved

  • Encapsulated Commands: Each action is a reusable, undoable object
  • Decoupled UI/Logic: Invoker doesn’t know how a command works
  • Undo Support: Each command tracks and reverts its own effect
  • Extensibility: Easily add PlayMusicCommandOpenGarageCommand, etc.
  • History Tracking: Command history enables undo/redo or logging