Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Command
LLD

Command

Encapsulate requests as objects to support queuing, logging, undo operations, and flexible invocation.

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:

CapabilityEnabled because
Queueing / schedulingCommands are data — put in queues, run later by workers
Logging / auditSerialize command + params before execution → replayable history
Undo/redoStore inverse info (next page)
Network transferMarshal command, execute remotely (RPC roots)
Macro compositionMacroCommand holds a list, executes in order

Real-World Sightings

  • Runnable/Callable handed 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

  1. Where does state live? In the command if it’s request parameters; in the receiver if it’s system state. Commands stay thin.
  2. Who creates commands? A factory/client role wires receiver+params — invokers never construct.
  3. 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.”

My Private Notes

Notes are auto-saved locally to this device.