Command: Decoupling Sender from Receiver
The Problem It Solves
A button must sometimes turn on a lamp, sometimes open a door, sometimes refund an order. Wiring the button directly to receivers means editing the button per receiver — and rules out everything that makes commands interesting: queuing requests, logging them, undoing them, sending them over networks. Command wraps a request as an object: what to do, on what target, with which arguments — decoupling whoever triggers (invoker) from whoever performs (receiver).
WITHOUT COMMAND WITH COMMAND
Button ──► knows Lamp Invoker ──► Command ◄──► Receiver
Button ──► knows Door (knows only (carries (does the work)
invoker coupled to every execute() everything
receiver; can't queue/log/undo interface) needed)
Mechanics
interface Command {
void execute();
}
// RECEIVER — owns the actual capability:
class Light {
void on() { /* wiring */ }
void off() { /* wiring */ }
}
// CONCRETE COMMAND — binds a request to a receiver:
class LightOnCommand implements Command {
private final Light light;
LightOnCommand(Light light) { this.light = light; }
@Override public void execute() { light.on(); }
}
// INVOKER — knows nothing about lights, doors, or refunds:
class RemoteButton {
private Command slot;
void setCommand(Command c) { this.slot = c; }
void press() { slot.execute(); } // polymorphism is the whole story
}
new RemoteButton().setCommand(new LightOnCommand(new Light()));
The invoker’s dependency surface is exactly Command — new capabilities never touch it.
Why Objects Beat Method Calls
A method call evaporates after execution. A command object persists:
| Capability | Enabled because |
|---|---|
| Queueing / scheduling | Commands are data — put in queues, run later by workers |
| Logging / audit | Serialize command + params before execution → replayable history |
| Undo/redo | Store inverse info (next page) |
| Network transfer | Marshal command, execute remotely (RPC roots) |
| Macro composition | MacroCommand holds a list, executes in order |
Real-World Sightings
Runnable/Callablehanded to executors — the JDK’s most-used command.- Swing
Action; menu/button frameworks everywhere. - Message queues carrying serialized intents (“RefundOrder(orderId)”) — command at system scale.
- Transaction logs / write-ahead logs: operations stored as data before application.
Design Decisions
- Where does state live? In the command if it’s request parameters; in the receiver if it’s system state. Commands stay thin.
- Who creates commands? A factory/client role wires receiver+params — invokers never construct.
- Sync or async execution? Same interface serves both; async adds completion callbacks/futures.
Trade-offs
- One class per operation grows large in command-heavy systems — mitigated by lambdas for trivial cases (
button.setOnClick(lamp::on)). - Indirection when reading: “what does this button do?” requires finding the wired command — worth it precisely when the dynamic capabilities above matter.
Interview Framing
- The scoring insight: “command converts an action into data, so anything you can do with data — store, send, schedule, reverse — you can do with actions.”
Premium Content
Unlock Command and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans