Skip to content

Encapsulating Requests

Turn a request into an object you can queue or undo

Builds on Interfaces

Stage 3core5 min read

Behavioral design patterns are about how objects work together and who does which job. They keep objects loosely coupled, which means one object does not depend much on another. That makes the code easier to change.

Imagine a remote control that sends commands to devices, like turning on the lights or changing the volume. The user does not need to know how the devices work inside. The user only needs to know which buttons to press. The Command Pattern gives code the same setup.


1. What is the Command Pattern?

The Command Pattern is a behavioral design pattern. It turns a request into its own object. The code that sends the request is then separate (decoupled) from the code that carries it out.

Formal Definition

The Command Pattern wraps a request inside an object (encapsulates it). Because a request is now an object, you can pass different requests to a client, put requests in a queue, and keep a log of them. The pattern makes features like undo, redo and logging possible without changing the core business logic. This demo builds only undo.

You can also run a command later, without knowing the details of how it is carried out.

Real-Life Analogy

Think of a remote control for the lights or an air conditioner (AC). You press a button. You do not need to know how the circuits work or how the AC receives the signal. You press "On" or "Off", and the remote sends the command.

In the same way, the Command Pattern separates the sender of a request (the remote control) from the receiver (the light or AC). Commands become simpler to handle and easier to change.


2. Four Key Components

  • Client: Starts the request and creates the command object.
  • Invoker: Asks the command to run the request. It only calls execute().
  • Command: An interface that ties a receiver to one action. It declares execute() and undo().
  • Receiver: Knows how to do the real work for a request, such as turning a light on.

3. Understanding the Problem: Direct Execution Bloat

In a simple first version, the remote control calls the devices directly from its button methods.

Issues in the Naive Code

  • Tight Coupling: The NaiveRemoteControl class calls methods on the Light and AC classes directly. To add a device, you must change the remote control class. This breaks the Open/Closed Principle (OCP): code should be open for new features but closed to edits of working code.
  • Lack of Flexibility: The commands are written into the remote control class. For a new action or a different order of actions, you must edit the remote control code. That makes it harder to maintain.
  • Brittle Undo Functionality: The pressUndo method remembers only a short string and uses a switch statement. This makes complex undo hard to add, especially with many actions or devices.
  • Hardcoded Commands: The remote control class defines each command as a method, such as pressLightOn and pressACOn. The system is rigid and hard to change.
  • No Command History: The first version has no single place that records the commands already run, in order. So multi-level undo (undoing several steps back) is hard to build.

4. Class Diagram & Structural Layout

The Command Pattern puts a layer (the Command interface) between the button slots and the devices. Each action becomes its own object, and all of them can be used through the same interface (polymorphism).

Loading diagram…

5. How the Command Pattern Resolves the Issues

IssueHow Command Pattern Resolves the Issue
Tight CouplingThe RemoteControl class no longer touches devices. It talks only to the Command interface, so the code that triggers an action is fully separate from the devices.
Lack of FlexibilityA new action or device is added as a new Command class. The RemoteControl class is not edited.
Undo FunctionalityEvery command has the same undo() method. Each concrete command knows its own opposite action, so undo is safe and kept in one place.
Hardcoded CommandsBecause all commands share one interface, you can assign any command to any button slot while the program runs.
Maintaining Command HistoryA real stack (commandHistory) records the commands already run. Undo pops the latest one, so you can step back many times. The stack has no size limit, but this demo has no redo.

6. Impact Without the Command Pattern

  • Tight Coupling Between Invoker and Receiver: The button logic and the devices stay hard-wired. If a device class changes, the remote breaks.
  • Lack of Reusability: Actions written inside the remote cannot be shared with a scheduler or a macro unless you copy the code.
  • Complex Undo/Redo Operations: Undo is error-prone, because developers must track every change by hand with strings or indexes.
  • Difficulty in Implementing Batch Actions: Grouping actions (e.g., a "Night Mode" that turns off the lights and lowers the AC together) is awkward, because actions cannot be put inside a bigger action (a composite). The demo does not build one.
  • No Plug-and-Play Flexibility: The remote stays fixed. It cannot change its buttons while the program runs.

7. Operational Trade-Offs

Pros

  • Decouples Sender and Receiver: The code that triggers actions is kept apart from the device code, so it is easier to change and maintain.
  • Supports Clean Undo/Redo Functionality: Commands can be recorded as they run, so reversing them is simple. The demo code implements undo only, with no redo.
  • Extensible and Reusable Architecture: It follows the Open/Closed Principle. You can add new commands without breaking existing code.

Cons

  • Class Count Explosion: You get many more classes, because every action needs its own class.
  • Unnecessary Complexity for Simple Tasks: Interfaces and history stacks are too much (over-engineering) for a small program with only one or two fixed actions.
  • Requires Careful State Design for Undo/Redo: Undoing a command correctly takes careful design when the command depends on complex state or on state shared with other commands.

8. When to Use the Command Pattern

  • Decoupling Requirements: When the object that carries out a command should not know who triggered it.
  • Undo/Redo Lifecycles: When you need reliable undo, one step at a time. This demo gives undo only.
  • Macro Actions: When you want to group several actions into one command (a composite command) and run them in order. The pattern allows this, but this demo has no composite command.
  • Plug-in System Design: When you are building a plug-in style system, where new operations can be added while the program runs.

In this topic

Done with this topic?

How well do you know it?

PreviousObserver Pattern: Event-Driven Communication