Command Pattern — A Quick Guide
Design Patterns: Command Pattern
--
“Command Pattern encapsulates a request as an object, helping us to use it with different requests and support undo operation.”
Command Pattern is like ordering at a restaurant.
Chef does not hear orders from the client itself, he/she uses a helper.
(like a waiter/waitress)
Suppose you have an air conditioner and you need to turn it on/off.
Normally in this use case, you would simply write an AirConditioner class with turnOn() and turnOff() methods, but this time we’ll implement the actions in classes instead of methods.
To do this, you can write a helper class such as RemoteControl.
Now it’s like this:
We are the client, remote control is the waiter/waitress and AC is the chef.
Let’s see it in code:
public class AirConditioner {
private boolean active;
void turnOn() {
active = true;
}
void turnOff() {
active = false;
}
}
You need separate classes for on/off actions (commands), such as:
public interface Command {
void execute();
void undo();
}
public class AirConditionerOff implements Command {
private final AirConditioner airConditioner;
public AirConditionerOff(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
@Override
public void execute() {
airConditioner.turnOff();
}
@Override
public void undo() {
airConditioner.turnOn();
}
}
public class AirConditionerOn implements Command {
private final AirConditioner airConditioner;
public AirConditionerOn(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
@Override
public void execute() {
airConditioner.turnOn();
}
@Override
public void undo() {
airConditioner.turnOff();
}
}
And you need a helper (invoker) to communicate between us and AC:
public class RemoteControl {
private RemoteControl(){} // hiding the constructor
public static void submit(Command command) {
command.execute();
}
public static void undo(Command command) {
command.undo();
}
}
And the client code would be like:
AirConditioner airConditioner = new AirConditioner();
Command turnOn = new AirConditionerOn(airConditioner);
Command turnOff = new AirConditionerOff(airConditioner);RemoteControl.submit(turnOn); // turned air conditioner on
RemoteControl.undo(turnOn); // turned air conditioner offRemoteControl.submit(turnOn); // turned air conditioner on
RemoteControl.submit(turnOff); // turned air conditioner off
Multiple Commands & Stack
What if you want to create an app with multiple commands, not just on/off?
public class AirConditioner {
private int power;
void off() {
power = Power.OFF; // OFF = 0
}
void low() {
power = Power.LOW; // LOW = 1
}
void medium() {
power = Power.MEDIUM; // MEDIUM = 2
}
void high() {
power = Power.HIGH; // HIGH = 3
}
}public interface Command {
void execute();
// undo is gone from here for now, we'll see it below later}
public class AirConditionerOff implements Command {
private final AirConditioner airConditioner;
public AirConditionerOff(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
@Override
public void execute() {
airConditioner.off();
}
}
public class AirConditionerLowPower implements Command {
private final AirConditioner airConditioner;
public AirConditionerLowPower(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
@Override
public void execute() {
airConditioner.low();
}
}
public class AirConditionerMediumPower implements Command {
private final AirConditioner airConditioner;
public AirConditionerMediumPower(AirConditioner airConditioner){
this.airConditioner = airConditioner;
}
@Override
public void execute() {
airConditioner.medium();
}
}
public class AirConditionerHighPower implements Command {
private final AirConditioner airConditioner;
public AirConditionerHighPower(AirConditioner airConditioner){
this.airConditioner = airConditioner;
}
@Override
public void execute() {
airConditioner.high();
}
}
But this time undo operation is a bit more complicated. Since now there are multiple commands, you need to know the exact steps client took.
To achieve this you can use Stack data structure.
import java.util.Stack;
public class RemoteControl {
private RemoteControl() {}
private static final Stack<Command> undoStack = new Stack<>();
private static final Stack<Command> redoStack = new Stack<>();
public static void submit(Command command) {
command.execute();
undoStack.add(command);
redoStack.clear();
}
public static void undo() {
if (!undoStack.isEmpty()) {
undoStack.peek().execute();
redoStack.add(undoStack.pop());
}
}
public static void redo() {
if (!redoStack.isEmpty()) {
redoStack.peek().execute();
undoStack.add(redoStack.pop());
}
}
}
Now we should be able to use multiple commands and also support undo/redo operation. :)
Let’s see it in client code:
AirConditioner airConditioner = new AirConditioner();
Command highPower = new AirConditionerHighPower(airConditioner);
Command mediumPower = new AirConditionerMediumPower(airConditioner);
Command lowPower = new AirConditionerLowPower(airConditioner);
Command off = new AirConditionerOff(airConditioner);// feel free to apply any creational design pattern above
RemoteControl.submit(highPower); // air conditioner set HIGH
RemoteControl.submit(lowPower); // air conditioner set LOW
RemoteControl.submit(mediumPower); // air conditioner set MEDIUM
RemoteControl.undo(); // air conditioner set LOW
RemoteControl.undo(); // air conditioner set HIGH
RemoteControl.redo(); // air conditioner set LOW
RemoteControl.submit(off); // air conditioner set OFF
It works!
Thank you for reading and being a part of my journey!
Reference(s)
- Head First Design Patterns. by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra. Released October 2004. Publisher(s): O’Reilly Media, Inc.
- Dive into Design Patterns. by Alexander Shvets, Released 2019.