Skip to content

Encapsulating Requests

Turn a request into an object you can queue or undo

Builds on Interfaces

Stage 3core3 min read

The current design ties the remote control tightly to the devices (the light and the air conditioner). The remote calls the device methods itself. It also undoes actions by remembering a loose text string. The design becomes rigid and hard to extend.


1. Understanding the Problem

Imagine a simple home automation remote where button presses switch devices like a Light or an AC on and off.

In a simple first version, the remote control holds a real object for every appliance type. When a button is pressed, the remote itself must call the right method on the right device. So the remote and the devices are tied together.


2. Real-Life Analogy

Think of a television remote control. When you press "Volume Up", the remote cannot change the sound itself. It only sends a signal. It does not care if the receiver is a soundbar, an old TV or a projector, as long as the receiver understands the signal.

Our simple first design breaks this idea. The remote must know exactly how every appliance in the room works.


3. Four Key Components at Risk

To see why this code is fragile, look at how it misuses the four key components of the Command Pattern:

  • Client: The setup code must connect real device classes straight into the remote, fixed when the code is compiled.
  • Invoker (NaiveRemoteControl): It grows large, because it needs a separate method for every action.
  • Command: Missing. Nothing represents the "request" as its own object. The request is only code inside the remote class.
  • Receiver (Light / AC): Open to the invoker, which must know its exact methods (on(), off()).

4. Architectural Deficiencies in the Naive Code

Tight Coupling

The NaiveRemoteControl class holds direct references to Light and AC. To add a new appliance (e.g., a SmartTV or a Geyser), you must edit the remote control class. You add new private fields and change the constructor. This breaks the Open/Closed Principle (OCP): code should be open for new features but closed to edits of working code.

Lack of Flexibility & Hardcoded Commands

Each command is a fixed method like pressLightOn() or pressACOn(). The setup is static. You cannot give a button a different action while the program runs. You also cannot easily make one button run a series of actions (a macro), such as a "Leave Home" button that switches off every appliance.

Primitive Undo Functionality

The pressUndo() method uses a fragile switch block that compares strings ("LIGHT_ON", "AC_OFF"). The remote must work out how to reverse each action. This is easy to get wrong, and the switch grows with every new appliance.

Inability to Maintain True Command History

The remote remembers only one string variable (lastAction), so it keeps a history of just one step. Worse, pressUndo() sets lastAction to the opposite action after it reverses something. So a second undo does not go back a step. It undoes the first undo and redoes the action. In Main, after LIGHT_ON, AC_ON, LIGHT_OFF, the first undo turns the light on, and the second undo turns it off again. The AC is never reached. Real multi-level undo is impossible to build this way.


5. Structural Breakdown of Vulnerabilities

Operational MetricNaive Design StatusArchitectural Target
ExtensibilityLow: Modifying hardware requires updating the remote console.High: Plug new devices in via uniform abstractions.
Undo SupportFragile: Done with string checks inside switches.Strong: Command objects carry their own reversal logic.
History RetentionShallow: Remembers only the very last change.Deep: A growing history stack keeps every step.
SRP ComplianceViolated: The remote handles clicks and the rules for reversing devices.Followed: The remote handles triggers; commands handle logic.

6. Summary of the "Smell"

When the part that triggers an action must know the exact details of the class that carries it out, the request has not been wrapped in its own object.

The Command Design Pattern fixes this. It turns the request into its own object with its own execute() and undo() methods. All commands share one interface (polymorphism). The trigger and the receiver are then fully separate.

In this topic

Before code1 file

Before/Main.java121 linesGitHub
package DesignPatterns.BehaviouralPatterns.CommandPattern.Before;// Receiver classes - Light and AC with basic on/off methodsclass 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");    }}/** * PROBLEM: Monolithic Control & Rigid Dependencies. * The 'NaiveRemoteControl' (Invoker) is directly aware of 'Light' and 'AC' (Receivers). * It hardcodes individual methods for every device state, destroying extensibility. */class NaiveRemoteControl {    // ISSUE 1: VIOLATION OF OPEN/CLOSED PRINCIPLE (OCP)    // If you add a "SmartTV" or "Geyser" to the smart home system, you must rewrite    // this class to add new fields and constructor arguments.    private Light light;    private AC ac;    // ISSUE 2: LIMITED HISTORY RETENTION    // Storing a simple string only allows a single level of history tracking.    // If you press three buttons, you can only reverse the absolute last one,    // and subsequent calls will cycle unexpectedly or fail to track true historical depth.    private String lastAction = "";    public NaiveRemoteControl(Light light, AC ac) {        this.light = light;        this.ac = ac;    }    // ISSUE 3: EXPLODING INTERFACE METHOD COUNT    // Adding 10 new devices means adding 20-30 new explicitly named click methods.    public void pressLightOn() {        light.on();        lastAction = "LIGHT_ON";    }    public void pressLightOff() {        light.off();        lastAction = "LIGHT_OFF";    }    public void pressACOn() {        ac.on();        lastAction = "AC_ON";    }    public void pressACOff() {        ac.off();        lastAction = "AC_OFF";    }    /**     * ISSUE 4: SCATTERED STATE-REVERSAL BUSINESS LOGIC     * The invoker has to reverse-engineer *how* to invert an operational task.     * The switch block maps strings back to inverted operations, making the method     * fragile and bloated. This belongs in isolated command abstractions.     */    public void pressUndo() {        switch (lastAction) {            case "LIGHT_ON":                light.off();                lastAction = "LIGHT_OFF";                break;            case "LIGHT_OFF":                light.on();                lastAction = "LIGHT_ON";                break;            case "AC_ON":                ac.off();                lastAction = "AC_OFF";                break;            case "AC_OFF":                ac.on();                lastAction = "AC_ON";                break;            default:                System.out.println("No action to undo.");                break;        }    }}// Client Codepublic class Main {    public static void main(String[] args) {        Light light = new Light();        AC ac = new AC();        NaiveRemoteControl remote = new NaiveRemoteControl(light, ac);        remote.pressLightOn();        remote.pressACOn();        remote.pressLightOff();        // BUG TRACKING DEMO:        // Because history is managed as a single shallow text variable, multiple calls        // to pressUndo will not walk back down an execution stack safely.        remote.pressUndo(); // Should undo LIGHT_OFF -> Light ON        remote.pressUndo(); // BUG: Will overwrite state randomly or hit default conditions!    }}

Done with this topic?

How well do you know it?

PreviousObserver Pattern: Event-Driven Communication