Overview
The Command Pattern encapsulates requests as objects, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. In this pattern, the requester sends a command to execute an operation, while the receiver gets the request and performs the action. This pattern decouples the object that invokes the operation from the one that knows how to perform it, making systems more flexible and extensible. The Command Pattern is classified as a behavioral design pattern.
In typical software systems, the component that requests an action and the component that performs it tend to be tightly coupled. While this approach offers straightforward implementation, it creates significant limitations when requirements change.
When you need to track, undo, or redo operations, tightly coupled code forces you to modify the original source. The Command Pattern solves this by introducing an abstraction layer—the command interface—between the requester and the implementer. Since the intermediate layer is abstract with various implementations available, the system gains excellent extensibility. The core purpose of this pattern is to decouple the command request from its execution logic.
When to Use the Command Pattern
Apply this pattern when an operation in your system carries command semantics, and the implementation is likely to change. By decoupling through an abstract command interface, the calling code remains stable while implementation details are encapsulated. The receiver and abstract command interface maintain loose coupling, meaning internal methods don't need to match exactly, which provides great flexibility for future changes. The Command Pattern fits well in these scenarios:
- Operations with command semantics in real-world contexts—such as menu commands, shell commands, or batch operations
- Situations where the invoker and receiver must remain independent without direct interaction
- Scenarios requiring deferred execution of behaviors, including undo and redo functionality
- Requirements for macro commands (combinations of multiple commands)
Class Diagram Structure
The Command Pattern architecture involves four key participants:
Receiver: The class responsible for carrying out the actual work requested. It contains the business logic that responds to the command.
Command Interface: An abstract contract that declares the operation all concrete commands must implement.
Concrete Command: A class that holds a reference to a receiver and implements the command interface. Its execution method delegates to the corresponding method on the receiver it maintains.
Invoker: The component that receives commands from the client and triggers their execution.
Looking at this diagram, the Command interface acts as a intermediary between the Receiver and Invoker, breaking their direct dependency. This architectural decision serves two primary purposes:
Decoupling the request from execution removes the direct link between Invoker and Receiver. The Invoker is concrete and tied to client requests, sitting in the business logic layer where it should remain stable. The Receiver, however, handles functional details that change frequently. Without Command as an intermediary, a stable component would depend on an unstable one, compromising the entire architecture. The Command layer provides stability by allowing stable components to depend on the abstraction rather than volatile implementations.
Enhanced extensibility emerges from two directions. First, Receivers represent implementation details that can be swapped out to achieve different behaviors. Second, the Command interface itself is abstract by design, inherently extensible. Since command objects are inherently polymorphic, combining this pattern with the Decorator Pattern becomes straightforward for adding capabilities.
Real-World Applications
- Television Remote Control: The remote acts as the invoker, sending commands to the television (receiver) without knowing how the TV processes them.
- Restaurant Order System: A waiter takes orders and passes them to the kitchen, decoupling customers from kitchen operations.
Pattern Usage in Frameworks
1. java.lang.Runnable Interface
The Runnable interface serves as a command abstraction in Java's concurrency framework. Any class implementing Runnable represents a unit of work suitable for execution by a thread.
public interface Runnable {
void execute();
}
When you invoke the start() method on a thread, it becomes eligible to compete for CPU resources without requiring manual resource management. Once the thread acquires CPU time, it executes the code in the execute() method. This interface effectively separates the client's work request from the actual CPU execution logic.
2. junit.framework.Test Interface
JUnit's Test interface defines the contract for test components in the testing framework.
public interface Test {
int getTestCaseCount();
void execute(TestResults results);
}
The getTestCaseCount() method returns how many test cases the implementing class contains. The execute() method runs the actual test logic, with the TestResults parameter collecting outcomes and any failures encountered.
When creating test cases, implementing this interface marks a class as a testable component. In practice, developers extend the TestCase base class, which already fulfills this contract.
public abstract class TestCase extends Assert implements Test {
// Internal state and helper methods
public void execute(TestResults results) {
results.run(this);
}
// Additional test infrastructure
}
By extending TestCase, a class automatically satisfies the Test interface requirements and becomes recognized by JUnit's test runner as a proper test case.
Advantages and Trade-offs
Benefits:
- Introducing an abstraction layer effectively decouples the command request from its implementation details
- Excellent extensibility makes adding new commands straightforward
- Built-in support for command composition and queuing operations
- Enables layering additional functionality on top of existing commands through decorators or other patterns
Drawbacks:
- Large numbers of concrete command classes can emerge for complex systems
- The pattern introduces additional class complexity—command requests and their implementations are separated by abstraction layers, which can increase cognitive overhead. This is inherent to design patterns; abstraction adds types that wouldn't exist in simpler, tightly coupled solutions, and abstract concepts naturally require more effort to understand than direct implementations.