Design Pattern (19) Command Pattern: Smart Home Remote Control System, Implementing Operation Encapsulation and Undo Mechanism
You can download the Design Pattern series code from this design_pattern repo.
Requirements
We need to build a music player control system. This system will demonstrate how to elegantly manage different operation commands, with the following core requirements:
- Remote Control: Users can control the music player through a remote control to perform basic operations. Supported operations include “play”, “pause”, and “stop” functions.
- Undo Mechanism: The system must provide undo functionality. For example, undoing a pause operation will automatically restore the play state.
- Extensibility: Button behaviors should remain flexible and easily extensible. Future advanced functions like “next song” and “replay” may need to be added.
Why Do We Need Design Patterns?
This requirement scenario highlights the core value of behavioral design patterns. When a system needs to handle multiple different operation commands, traditional direct calling approaches often lead to excessive code coupling.
We need to find a way to effectively manage the interaction relationship between operation requests and executors. This way, the system can flexibly handle different behavioral operations while maintaining good maintainability.
Object-Oriented Analysis (OOA)
After understanding the requirements, we perform object-oriented analysis. At this stage, we need to identify the main roles in the system and their interaction relationships.
System Role Identification
From the requirement analysis, our system contains the following key elements:
- Remote Control: Responsible for initiating operation requests, is the behavior trigger
- Music Player: Actually executes operations, handles specific playback logic
- Various Operation Commands: Including play, pause, stop, and other different behavior commands
- Operation History Management: Tracks command execution history, supports undo functionality
Design Thinking Focus
This architecture demonstrates typical behavior separation design thinking. We separately handle request initiation, transmission, and execution, giving each component clear responsibilities.
Through this analysis, we can clearly see the positioning and relationships of each role in the system, laying the foundation for subsequent pattern application.

Recognizing Forces
In direct implementation without design patterns, we encounter the following core challenges:
1. High Coupling Problem (High Coupling)
Problem Description: The client (remote control) needs to directly operate each specific device’s functionality. This direct dependency leads to excessively high system coupling, which is not conducive to subsequent expansion.
Specific Impact: When the remote control directly calls player.play()
, player.pause()
and other methods, it must understand the player’s specific interface. This tight coupling makes the system rigid and difficult to test.
2. Lack of Flexibility (Lack of Flexibility)
Problem Description: When new devices or operations need to be added, the client must modify a large amount of code. This violates the Open-Closed Principle and significantly increases maintenance costs.
Practical Case: To add a “repeat play” function, not only does the player class need to be modified, but all related remote control logic must also be updated. This chain reaction makes system maintenance extremely difficult.
3. Undo Mechanism Complexity (Undo/Redo Complexity)
Core Problem: The system lacks a unified operation history management mechanism. Implementing undo and redo functionality becomes extremely difficult, with each operation requiring individual rollback logic handling.
Traditional Dilemma: Traditional approaches require manually implementing corresponding reverse operations for each operation. This not only increases code complexity but also easily creates state inconsistency problems.
Root Problem Analysis
The root of these problems lies in the lack of an appropriate abstraction layer between request initiators and request executors.
Without this abstraction layer, behavior definition, execution, and management are tightly intertwined. We need a mechanism to decouple these relationships, making the system more flexible and maintainable.
Applying Command Pattern to Solve Problems
After object-oriented analysis and recognizing the challenges the system faces, we can apply the Command Pattern to solve these problems.
Core Philosophy of Command Pattern
Command Pattern is a behavioral design pattern that encapsulates requests as objects. This encapsulation method is the essence of behavioral patterns: representing and managing behaviors through objects rather than directly executing behaviors.
Advantages Brought by the Pattern
This pattern allows us to:
- Parameterize clients with different requests
- Queue operations or log them
- Support undo and redo operations
- Completely separate behavior definition from execution
Core Concepts
More importantly, the Command Pattern embodies the core concept of behavior management. It simplifies complex object interaction relationships into a unified command interface, making the system easier to understand and maintain.
Through this pattern, we can completely separate “what to do” from “how to do it”, achieving true separation of concerns.
Core Structure of Command Pattern

Roles and Responsibilities
Command Pattern solves system problems through the following five core roles, each undertaking specific behavior management responsibilities:
1. Receiver - Music Player
Responsibility Definition: The object that actually executes music playback logic. It knows how to execute specific business operations such as play, pause, and stop functions.
Design Philosophy: In behavioral patterns, the Receiver represents the final executor of behavior. It focuses on implementing business logic without needing to care about who initiated the request or how to manage these requests.
2. Command (Command Interface)
Responsibility Definition: Defines the common interface for all commands. Ensures each command has both executability (Execute) and reversibility (Undo) capabilities.
Importance: This interface is the core abstraction of the entire pattern, unifying the calling method of behaviors. It allows all different operations to be managed through the same interface.
3. ConcreteCommand (Concrete Command)
Responsibility Definition: Encapsulates specific playback control operations into command objects. Each concrete command (such as “play command”, “pause command”, “stop command”) knows how to execute and undo its own operation.
Core Value: Concrete commands implement behavior encapsulation. They wrap complex operation logic into simple objects while maintaining state information needed for execution.
4. Invoker - Remote Control
Main Function: Responsible for command execution caller. It doesn’t need to know specific command implementation details, only needs to call the command’s execute() method.
Additional Responsibility: Also tracks command history to support undo functionality. The Invoker embodies behavior coordination management, responsible for controlling command execution timing and order.
5. Client
Core Task: Responsible for creating concrete command objects and setting their receivers. Establishes associative relationships between commands, receivers, and invokers.
Role Positioning: The Client plays the role of behavior organizer, responsible for constructing the entire command execution system but not directly participating in command execution.
Application to Music Player System

Object-Oriented Programming Implementation
Next, we’ll transform Command Pattern theory into actual code. Each component has clear responsibility division, forming a complete command execution system.
Implementation Focus
This implementation demonstrates the essence of behavioral patterns: managing complex behavioral interactions through object collaboration rather than letting a single object bear too many responsibilities.
Let’s examine each component’s implementation step by step to understand how they work together to solve the original design problems.
Receiver - Music Player
The music player, as the receiver, is responsible for actually executing playback-related business logic. This class focuses on core business functionality without needing to understand who will call these methods or how to manage these calls:
class MusicPlayer {
fun play() {
println("Music is playing")
}
fun pause() {
println("Music is paused")
}
fun stop() {
println("Music is stopped")
}
}
Command - Command Interface
The command interface defines the standard protocol that all commands must implement.
Design Goal: This interface ensures consistency in behavior execution, making all commands follow the same calling convention:
interface Command {
fun execute()
fun undo()
}
ConcreteCommand - Concrete Command Implementation
Each concrete command encapsulates a specific operation and knows how to execute and undo that operation.
Core Concept: These classes demonstrate the concept of command objectification, converting behaviors into objects that can be stored, passed, and managed:
class PlayCommand(private val player: MusicPlayer) : Command {
override fun execute() {
player.play()
}
override fun undo() {
player.pause() // Undo play means pause
}
}
class PauseCommand(private val player: MusicPlayer) : Command {
override fun execute() {
player.pause()
}
override fun undo() {
player.play() // Undo pause means play
}
}
class StopCommand(private val player: MusicPlayer) : Command {
override fun execute() {
player.stop()
}
override fun undo() {
println("Cannot undo stop") // Undo stop usually cannot be recovered
}
}
Invoker - Remote Control
The remote control, as the command invoker, manages command execution and history recording.
Scope of Responsibility: It implements behavior coordination control, responsible for deciding when to execute commands and how to handle command history:
class RemoteControl {
private val commandHistory = mutableListOf<Command>()
fun pressButton(command: Command) {
command.execute()
commandHistory.add(command)
}
fun pressUndo() {
if (commandHistory.isNotEmpty()) {
val lastCommand = commandHistory.removeLast()
lastCommand.undo()
} else {
println("No command to undo")
}
}
}
Client - Client Usage Example
The client demonstrates how to create and use the entire command system.
Design Highlight: Notice how the client constructs a complete behavior management system through combining different objects without needing to care about internal complex interactions:
fun main() {
val player = MusicPlayer()
val playCommand = PlayCommand(player)
val pauseCommand = PauseCommand(player)
val stopCommand = StopCommand(player)
val remoteControl = RemoteControl()
// Play music
remoteControl.pressButton(playCommand)
// Pause music
remoteControl.pressButton(pauseCommand)
// Undo
remoteControl.pressUndo()
// Stop music
remoteControl.pressButton(stopCommand)
// Undo
remoteControl.pressUndo()
}
Execution Results
The program output results are as follows:
Music is playing
Music is paused
Music is playing
Music is stopped
Cannot undo stop
Conclusion and Benefits
By applying the Command Pattern, we successfully solved the core problems originally faced by the system. This fully demonstrates the powerful capability of behavioral design patterns in managing complex object interactions.
Main Improvement Effects
1. Reduced Coupling
Improvement Results: The client no longer directly depends on specific devices but operates through command interfaces. This significantly improves system modularity.
Design Advantages: Clear abstraction boundaries are established between behavior initiators and executors, allowing each component to focus on its core responsibilities.
2. Enhanced Flexibility
Extension Convenience: When adding new functions, only new command classes need to be created without modifying existing code. This fully complies with the Open-Closed Principle.
Long-term Benefits: This design gives the system excellent behavior extensibility, easily adapting to constantly changing business requirements.
3. Simplified Undo Mechanism
Implementation Simplification: Through unified command interfaces and history management, undo and redo functionality becomes concise and maintainable.
Automated Management: Each command comes with its own undo logic, forming a behavior self-management mechanism that significantly simplifies complex operation state management.
Value Embodiment of Behavioral Patterns
Command Pattern perfectly interprets the core philosophy of behavioral design patterns:
- Behavior Abstraction: Converting complex operations into simple objects
- Responsibility Separation: Letting each object focus on specific behavior management tasks
- Interaction Simplification: Reducing complex dependencies between objects through unified interfaces
Applicable Scenarios
Command Pattern is particularly suitable for the following application scenarios:
- GUI Button Operations: Each button corresponds to a command object, achieving separation of behavior and interface
- Batch Operation Systems: Scheduling operations and executing them sequentially, supporting complex workflow management
- Operation History Recording: Applications requiring Undo/Redo functionality, such as text editors and drawing software
- Remote Invocation: Encapsulating local requests and sending them to remote execution, implementing distributed system behavior coordination
Core Value Summary
The core value of this pattern lies in completely decoupling behavior requesters from behavior executors. Through this decoupling, system architecture becomes clearer and easier to extend.
Through Command Pattern, we not only solved current technical problems but also established a system architecture with excellent behavior management capabilities. This lays a solid foundation for future feature expansion, allowing the system to continuously evolve and improve.
Enjoy Reading This Article?
Here are some more articles you might like to read next: