Command Pattern
Design Patterns: Command Pattern.
The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object that contains all the information about the request. This transformation allows you to parameterize methods with different requests, queue requests, and log their execution, among other things. At its core, it decouples the object that initiates an action from the object that performs the action.
In Java, it abstracts the execution of a specific action into a dedicated class. This pattern separates the objects that invoke an operation (the Invoker) from the objects that know how to perform it (the Receiver).
Implementing the Command Pattern
To implement this pattern, we define four core roles:
- Command: An interface with a single
execute()method. - Receiver: The object that performs the actual action.
- Concrete Command: Implements the
Commandinterface and binds the Receiver and its action. - Invoker: Holds a reference to a Command object and calls the
execute()method.
In this example, we model a simple remote control. The RemoteControl (Invoker) doesnโt know how to turn a light on or off; it just issues a command to the Light (Receiver).
Step 1: Create the Command Interface
// Command Interface
public interface Command {
// The method to execute the command
void execute();
// Optional: method for undo capability
void undo();
}
Step 2: Create the Receiver Class
This object performs the actual business logic. It has no knowledge of the command object.
// Receiver
class Light {
public void turnOn() {
System.out.println("Light is ON.");
}
public void turnOff() {
System.out.println("Light is OFF.");
}
}
Step 3: Create Concrete Command Classes
These classes implement the command interface and hold a reference to the Receiver.
// Concrete Command 1: Turn Light On
class LightOnCommand implements Command {
private Light light; // Reference to the Receiver
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
// Delegates the request to the Receiver
light.turnOn();
}
@Override
public void undo() {
// Reverse the action for undo support
light.turnOff();
}
}
// Concrete Command 2: Turn Light Off
class LightOffCommand implements Command {
private Light light; // Reference to the Receiver
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
@Override
public void undo() {
light.turnOn();
}
}
Step 4: Create the Invoker Class
The Invoker holds the command object and triggers its execution. It is decoupled from the specific command or receiver logic.
// Invoker
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
// Invoker just calls execute, not knowing what it does
System.out.println("Remote button pressed...");
command.execute();
}
public void pressUndo() {
System.out.println("Remote UNDO button pressed...");
command.undo();
}
}
Using Command in the Main Method
In the Java Main class, we configure the command objects and link them to the Invoker. This setup allows for maximum flexibility.
public class Main {
public static void main(String[] args) {
// 1. Create the Receiver
Light livingRoomLight = new Light();
// 2. Create the Concrete Command objects, linking them to the Receiver
Command lightOn = new LightOnCommand(livingRoomLight);
Command lightOff = new LightOffCommand(livingRoomLight);
// 3. Create the Invoker
RemoteControl remote = new RemoteControl();
// --- Sequence 1: Turn On and Off ---
remote.setCommand(lightOn);
remote.pressButton(); // Output: Light is ON.
remote.setCommand(lightOff);
remote.pressButton(); // Output: Light is OFF.
// --- Sequence 2: Demonstrate Undo ---
remote.setCommand(lightOn);
remote.pressButton(); // Light is ON.
// Use the undo feature
remote.pressUndo(); // Light is OFF.
}
}
Output:
Remote button pressed...
Light is ON.
Remote button pressed...
Light is OFF.
Remote button pressed...
Light is ON.
Remote UNDO button pressed...
Light is OFF.
Key Characteristics
- Decoupling: The Invoker is completely decoupled from the specific action performed. It only interacts with the generic
Commandinterface. - Parameterization: You can pass different command objects to the same Invoker to achieve different results.
- History and Undo Functionality: The pattern naturally supports history and an undo mechanism by adding an
undo()method to theCommandinterface, crucial in applications like text editors, where users may want to revert actions. - Queueing and Logging: Commands can easily be placed into a queue or logged to a file before execution (e.g., in transaction systems).
Why It Matters
- Flexibility: It makes it easy to add new commands without changing the Invoker class. Just create a new
ConcreteCommandclass. - Macro Commands: You can create a composite command (a macro) that holds a list of other commands, allowing a single
execute()call to trigger multiple actions. - Transaction Management: By using command objects, you can log all system operations, allowing for rollback or replay functionality.
Command Pattern is a fundamental concept used widely in Java applications for implementing complex features like transaction systems, toolbars, menu systems, and macro recording, all while maintaining loose coupling.