Command Design Pattern
The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.
Wow! There a lot to take from this one sentence. Before you go forward, it is important to note that command pattern is a data driven design pattern and falls under behavioral pattern category. Now lets step through it with the help of a design problem.
Consider, you are building a home automation system. There is a programmable remote which has a bunch of buttons. You can program these buttons to perform certain action(s), like turn on/off light(s), stereo, AC etc. Some of these buttons could be perform complex actions(more than one). For example, turning on a stereo could include setting the CD, controlling the volume and starting the stereo.
You can achieve the above, by implementing simple if-else as shown below. However, this creates a tight coupling and make it tough to reprogram.
if(buttonPressed == button1)
light.on()
else if(buttonPressed == button2)
light.off()
else if(buttonPressed == button3)
stereo.setCD();
stereo.setVolume();
stereo.start();
So what we want to achieve is a design that provides loose coupling and remote control should not have much information about the action it is performing.
Encapsulation of request means that the actions within a command is encapsulated from the remote. i.e. command binds together a set of actions to be perfomed on a receiver. It does so by exposing just one method execute()
that causes some actions to be invoked on the reciever.
Parameterize other objects with different requests means that the button used to turn on the lights can later be used to turn on the stereo.
Queue or log requests, and support undoable operations means that Command’s Execute operation can store state for reversing its effects in the Command itself. The Command may have an added unExecute()
operation that reverses the effects of a previous call to execute. It may also support logging changes so that they can be reapplied in case of a system crash.
Implementation
In a classic implementation, the command pattern requires implementing four components: Command, Receiver, Invoker and Client.
Command Class
A command is an object whose role is to store all the information required for executing an action, including the method to call, the method arguments and the object(known as the receiver) that implements the method.
public interface Command {
void execute();
}
public class TurnLightOnCommand implements Command {
private Light light;
public TurnLightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.on();
}
}
public class TurnLightOffCommand implements Command {
private Light light;
public TurnLightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.off();
}
}
In this case the Command
interface defines the command objects’ API, and the two implementations, TurnLightOnCommand
and TurnLightOffCommand
, perform the concrete actions. The former turns on the light and latter turns off the light.
Receiver Class
A receiver is an object that performs a set of cohesive actions. Its the component that performs the actual action when the command’s execute() method is called.
public class Light {
public void on() {
System.out.println("Light has been turned ON");
}
public void off() {
System.out.println("Light has been turned OFF");
}
}
Invoker Class
An invoker is an object that knows how to execute a given command but doesn’t know how the command has been implemented. It only knows the command’s interface.
public class SimpleRemoteControl {
private Command button1;
private Command button2;
public SimpleRemoteControl(Command button1, Command button2) {
this.button1 = button1;
this.button2 = button2;
}
public void pressButton1(){
button1.execute();
}
public void pressButton2(){
button2.execute();
}
}
Client Class
A client is an object that controls the command execution process by specifying what commands to execute and at what stages of the process to execute them.
public class HomeAutomationDemo {
public static void main(String[] args) {
// create the receiver
Light light = new Light();
// prepare the commands
Command turnLightOnCommand = new TurnLightOnCommand(light);
Command turnLightOffCommand = new TurnLightOffCommand(light);
// initiate invoker with the commands
SimpleRemoteControl remote = new SimpleRemoteControl(turnLightOnCommand, turnLightOffCommand);
// perform action
remote.pressButton1();
remote.pressButton2();
}
}
Source Code on Github