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.
| Issue | How Command Pattern Resolves the Issue |
|---|---|
| Tight Coupling | The 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 Flexibility | A new command (e.g., for a SmartTV or a Geyser) is a new class that implements Command. The RemoteControl class does not change. |
| Undo Functionality | Every command has the same undo() method. Each concrete command knows its own opposite action, so undo is safe and kept in one place. |
| Hardcoded Commands | Because all commands share one interface, you can assign any command to any button slot while the program runs. |
| Maintaining Command History | A 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
| Component | Responsibility |
|---|---|
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.