Skip to content

Encapsulating Requests

Turn a request into an object you can queue or undo

Builds on Interfaces

Stage 3core3 min read

The refactored code uses the Command Design Pattern. A request becomes its own object that holds everything needed to run it. So you can pass requests as parameters, queue them, and keep a history of them to reverse later.


1. How the Command Pattern Resolves the Issue

We put a layer, the Command interface, between the remote's buttons and the devices. The remote no longer depends on the devices directly. It works with commands.

IssueHow Command Pattern Resolves the Issue
Tight CouplingThe RemoteControl class no longer touches the devices. It works only with Command objects, which all share one interface (polymorphism). The trigger is fully separate from the work.
Lack of FlexibilityA new command (e.g., for a SmartTV or a Geyser) is a new class that implements Command. The RemoteControl class does not change.
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 Stack<Command> inside the invoker records each command in the order it ran. Undo takes the latest one off the stack, so you can step back many times. This demo has undo only, with no redo.

2. Impact Without the Command Pattern

If you skip the Command Pattern in a system built around many requests, problems pile up:

  • Tight Coupling Between Invoker and Receiver: The button logic and the devices are stuck together. Any change to a receiver, such as an update or a renamed class, breaks the invoker at once.
  • Lack of Reusability: Without a request object, an action cannot be reused elsewhere. If a timer service or an app dashboard needs to toggle the light, it must write the same code again.
  • Complex Undo/Redo Operations: A multi-step undo becomes a fragile, manual job, with large conditional logic buried inside the classes.
  • Difficulty in Implementing Batch Actions: Running a macro (such as one "Good Night" button that turns off the lights, locks the screens and puts the AC in eco-mode) gets messy, because each action must be wired by hand.
  • No Plug-and-Play Flexibility: The remote stays fixed. Buttons cannot change based on the situation or on what the user does while the program runs.
  • Scalability Bottlenecks: With dozens of devices and many states, the remote turns into a "God Object": one huge class that does everything and is too big to maintain.

3. Structural Breakdown

ComponentResponsibility
Receiver (Light / AC)Holds the real logic that does the work on the device.
Command Interface (Command)Declares the two methods every command has: execute() and undo().
Concrete Command (LightOnCommand, etc.)Links one receiver to one action.
Invoker (RemoteControl)Holds the button slots, runs the command in a slot, and records history.

4. Summary of the "Encapsulated Request"

Think of a restaurant. The Customer (Client) gives an order to the Waiter (Invoker). The Waiter writes every order on the same kind of Order Ticket (Command) and posts it on the board. The Cook (Receiver) reads the ticket and cooks the meal. If an order is cancelled, the ticket itself says how, so the Waiter never has to go into the kitchen and guess how to stop a recipe.

In this topic

After code1 file

After/Main.java217 linesGitHub
package DesignPatterns.BehaviouralPatterns.CommandPattern.After;import java.util.*;// ============================================================================// 1. RECEIVERS (The Operational Hardlinks)// ============================================================================/** * Receivers contain the actual underlying hardware or platform implementation logic. * They know *how* to execute the specific operation. Notice they have zero knowledge * of the Command interface or the RemoteControl invoker. */class Light {    public void on() {        System.out.println("Light turned ON");    }    public void off() {        System.out.println("Light turned OFF");    }}class AC {    public void on() {        System.out.println("AC turned ON");    }    public void off() {        System.out.println("AC turned OFF");    }}// ============================================================================// 2. COMMAND INTERFACE// ============================================================================/** * Establishes the uniform behavioral contract. By forcing every request to * wrap itself inside this interface, the Invoker can trigger any hardware * transaction polymorphically via a simple execute() call. */interface Command {    void execute(); // Encapsulates the execution pathway    void undo(); // Encapsulates the complete reversal logic}// ============================================================================// 3. CONCRETE COMMANDS (The Decoupled Requests)// ============================================================================/** * Concrete commands bind a specific action to a specific hardware Receiver. * They extract the operational execution logic away from the Invoker class. */class LightOnCommand implements Command {    // COMPOSITION: Holds a direct reference to the target receiver    private Light light;    public LightOnCommand(Light light) {        this.light = light;    }    @Override    public void execute() {        light.on(); // Delegating to receiver execution mechanics    }    @Override    public void undo() {        light.off(); // Knowing the exact operational inverse    }}class LightOffCommand implements Command {    private Light light;    public LightOffCommand(Light light) {        this.light = light;    }    @Override    public void execute() {        light.off();    }    @Override    public void undo() {        light.on();    }}class AConCommand implements Command {    private AC ac;    public AConCommand(AC ac) {        this.ac = ac;    }    @Override    public void execute() {        ac.on();    }    @Override    public void undo() {        ac.off();    }}class ACOffCommand implements Command {    private AC ac;    public ACOffCommand(AC ac) {        this.ac = ac;    }    @Override    public void execute() {        ac.off();    }    @Override    public void undo() {        ac.on();    }}// ============================================================================// 4. INVOKER (The Trigger Platform)// ============================================================================/** * The Invoker is completely decoupled from concrete devices like Light or AC. * It strictly tracks polymorphic `Command` reference slots, allowing it to execute * any current or future hardware command via unified button array indexes. */class RemoteControl {    // FLEXIBILITY: Generic Command slots allow dynamic runtime reassignment    private Command[] buttons = new Command[4];    // TRANSACTION HISTORY LAYER: Using a real data structure stack replaces    // primitive shallow tracking strings, enabling infinite multi-level undo operations.    private Stack<Command> commandHistory = new Stack<>();    // Setter Injection: Allows buttons to be bound or rebound dynamically at runtime    public void setCommand(int slot, Command command) {        buttons[slot] = command;    }    public void pressButton(int slot) {        if (buttons[slot] != null) {            buttons[slot].execute(); // Polymorphic activation            commandHistory.push(buttons[slot]); // Appending to the chronological transaction trail        } else {            System.out.println("No command assigned to slot " + slot);        }    }    /**     * UNIFIED UNDO RECOVERY     * Notice there are no switch statements or conditional evaluation branches here.     * The invoker simply pops the last transaction off the history pile and trusts     * the command object to safely roll back its own operational state change.     */    public void pressUndo() {        if (!commandHistory.isEmpty()) {            commandHistory.pop().undo(); // Self-contained inversion execution        } else {            System.out.println("No commands to undo.");        }    }}// ============================================================================// 5. CLIENT LAYER// ============================================================================/** * The Client configures the entire pipeline: instantiates the Receivers, * instantiates the concrete Command bindings, injects them into the Invoker slots, * and passes the control context to the runtime environment. */public class Main {    public static void main(String[] args) {        // 1. Initialize hardlinks (Receivers)        Light light = new Light();        AC ac = new AC();        // 2. Encapsulate actions inside standalone transaction wrappers (Commands)        Command lightOn = new LightOnCommand(light);        Command lightOff = new LightOffCommand(light);        Command acOn = new AConCommand(ac);        Command acOff = new ACOffCommand(ac);        // 3. Setup the control console platform (Invoker)        RemoteControl remote = new RemoteControl();        remote.setCommand(0, lightOn);        remote.setCommand(1, lightOff);        remote.setCommand(2, acOn);        remote.setCommand(3, acOff);        // 4. Runtime Simulations        System.out.println("--- Triggering Action Sequence ---");        remote.pressButton(0); // Slot 0: Light ON        remote.pressButton(2); // Slot 2: AC ON        remote.pressButton(1); // Slot 1: Light OFF        System.out.println("\n--- Triggering Sequential Undo Walkback ---");        remote.pressUndo(); // Pops LightOffCommand -> Executes undo() -> Light turns back ON        remote.pressUndo(); // Pops AConCommand    -> Executes undo() -> AC turns back OFF    }}

Done with this topic?

How well do you know it?

PreviousObserver Pattern: Event-Driven Communication