Behavioral Design Patterns
Imagine you are developing a "smart home" application with a UI that includes buttons to control various devices like lights, stereos, and garage doors.
A naive approach would be to code the logic directly inside each button's event listener:
public class SmartHomeUI {
private Light livingRoomLight = new Light();
private Stereo mainStereo = new Stereo();
public void onLightOnButtonClick() {
livingRoomLight.on();
}
public void onStereoOnButtonClick() {
mainStereo.on();
mainStereo.setCd();
mainStereo.setVolume(11);
}
// ... and so on
}
This UI class is now tightly coupled to the Light and Stereo classes. What happens when you want to add a Television? You have to modify the SmartHomeUI class. What if you want to reuse these actions in a different context, like a voice-activated assistant? You'd have to duplicate the logic.
Furthermore, how would you implement a global "undo" button? It's nearly impossible because the UI has no record of the actions it has performed; it just called methods directly.
The Command is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request. This object, the "command," serves as a decoupling layer between the object that invokes an operation and the object that knows how to perform it.
The invoker (e.g., a button) doesn't call a method on the receiver (e.g., a light) directly. Instead, it calls an execute() method on the command object. The command object itself holds a reference to the receiver and knows which of the receiver's methods to call. This encapsulation allows you to parameterize objects with operations, queue requests, and, most famously, support undoable operations.
execute(), and often an undo() method.Command interface. It holds a reference to a Receiver object and knows how to trigger the correct action on it.Light class). It has the business logic.RemoteControl button). It holds a Command object and calls its execute() method.Let's build a simple remote control using the Command pattern.
// The Command interface
public interface ICommand {
void execute();
void undo();
}
// The Receiver class
public class Light {
public void on() { System.out.println("Light is ON"); }
public void off() { System.out.println("Light is OFF"); }
}
// A Concrete Command to turn the light on
public class LightOnCommand implements ICommand {
private final Light light;
public LightOnCommand(Light light) { this.light = light; }
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off(); // The undo operation for "on" is "off"
}
}
// A Concrete Command to turn the light off
public class LightOffCommand implements ICommand {
private final Light light;
public LightOffCommand(Light light) { this.light = light; }
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
// The Invoker class
public class RemoteControl {
private ICommand command;
public void setCommand(ICommand command) {
this.command = command;
}
public void onButtonWasPushed() {
command.execute();
}
public void onUndoButtonWasPushed() {
command.undo();
}
}
// The Client, which wires everything up
public class Client {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();
Light livingRoomLight = new Light();
// Create commands and associate them with the receiver
ICommand lightOn = new LightOnCommand(livingRoomLight);
ICommand lightOff = new LightOffCommand(livingRoomLight);
// Turn the light on
remote.setCommand(lightOn);
System.out.println("--- Pushing ON button ---");
remote.onButtonWasPushed(); // Output: Light is ON
// Press undo
System.out.println("--- Pushing UNDO button ---");
remote.onUndoButtonWasPushed(); // Output: Light is OFF
}
}
Benefits:
RemoteControl (Invoker) is completely decoupled from the Light (Receiver). The remote could be reprogrammed to control a Stereo or a GarageDoor just by giving it a different command object, without changing its code.MacroCommand to execute them as a single batch operation.Drawbacks:
ConcreteCommand class, which can lead to a lot of boilerplate code.MacroCommand that executes a sequence of other commands is a direct application of the Composite pattern. The invoker can treat the macro (a composite) the same way it treats a single command (a leaf).undo() methods to roll back the entire transaction.