What is Command Pattern?
Command pattern is a behavioral pattern that encapsulates a request as an independent object, thereby letting you pass requests as method arguments, queue or log requests, and support undoable operation.
When to use it?
- Use Command pattern when you need to decouple an object making requests from the objects that know how to perform the requests.
- Use Command pattern when you want to inject or assign different requests to objects at runtime.
- Use Command pattern when you need undo or redo operation in your application.
Problem
Imagine we are designing API for a remote control that manipulates electronic devices in client house. Here we have lots of vender classes.
Introducing a common interface doesn't seem to be interesting since our vender classes are diverse, and it is also expected that we will have more vender classes.
Additionally, the remote control shouldn't consist of a set of if statements such as if slot1 == light, then light.turnOn(), else if slot1 == tv, then tv.turnOn()
because it is a bad design.
Solution
Simply put, we want the remote control to know how to make a generic request but not to care about a specific vender class. To achieve this, we are going to separate our concern into two classes, one makes a request and another one actually performs the work to comply the request.
RemoteControl
This is the one that makes a request such ascommand.execute()
, but doesn't care how to perform the work.
If you wantRemoteControl
to have multiple slots—for example, one forOutdoorLight
and another forTV
—RemoteControl
can have arrays of commands, such asonCommands[]
andoffCommands[]
. ThesetCommand
method can take an integer to determine which slot to set. For instance,setCommand(2, TVonCommand, TVoffCommand)
might assignTVonCommand
toonCommands[2]
andTVoffCommand
tooffCommands[2]
.Command
Provides interface for all the ConcreteCommands.ConcreteCommand
TurnOnOutdoorLightCommand object calls execute() method and delegates "how to turn on light" to the holding OurdoorLight object.OutdoorLight
This is the one that knows how to perform the work to comply the request from RemoteControl.Client
Client is responsible for creating ConcreteCommands and associates them with the corresponding Receivers.
Structure
Implementation in Java
// Receiver
public class OutdoorLight {
public void turnOn() {
System.out.println("Outdoor light is on");
}
public void turnOff() {
System.out.println("Outdoor light is off");
}
}
public interface Command {
void execute();
void undo();
}
// Concrete command
public class TurnOnOutdoorCommand implements Command {
private OutdoorLight outdoorLight;
public TurnOnOutdoorCommand(OutdoorLight outdoorLight) {
this.outdoorLight = outdoorLight;
}
@Override
public void execute() {
outdoorLight.turnOn();
}
@Override
public void undo() {
outdoorLight.turnOff();
}
}
// Concrete command
public class TurnOffOutdoorLightCommand implements Command {
OutdoorLight outdoorLight;
public TurnOffOutdoorLightCommand(OutdoorLight outdoorLight) {
this.outdoorLight = outdoorLight;
}
@Override
public void execute() {
outdoorLight.turnOff();
}
@Override
public void undo() {
outdoorLight.turnOn();
}
}
// Null Object
public class NoCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
// Invoker
public class RemoteControl {
private Command onCommand;
private Command offCommand;
public Stack<Command> undoCommands;
public RemoteControl() {
onCommand = new NoCommand();
offCommand = new NoCommand();
undoCommands = new Stack<>();
undoCommands.push(new NoCommand());
}
public void setCommand(Command onCommand, Command offCommand) {
this.onCommand = onCommand;
this.offCommand = offCommand;
}
public void onButtonWasPushed() {
onCommand.execute();
undoCommands.push(onCommand);
}
public void offButtonWasPushed() {
offCommand.execute();
undoCommands.push(offCommand);
}
public void undoButtonWasPushed() {
if (!(undoCommands.peek() instanceof NoCommand)) {
Command lastCommand = undoCommands.pop();
lastCommand.undo();
} else {
System.out.println("undoCommands stack is empty!");
}
}
}
public class Client {
public static void main(String[] args) {
OutdoorLight outdoorLight = new OutdoorLight(); // Create a receiver
// Create commands on that receiver
// These commands are encapsulated requests in other words
Command turnOnOutdoorLight = new TurnOnOutdoorCommand(outdoorLight);
Command turnOffOutdoorLight = new TurnOffOutdoorLightCommand(outdoorLight);
RemoteControl rc = new RemoteControl(); // Create an invoker
// We can pass encapsulated requests to other object
rc.setCommand(turnOnOutdoorLight, turnOffOutdoorLight); // Set command on that invoker
rc.undoButtonWasPushed();
rc.onButtonWasPushed();
rc.offButtonWasPushed();
rc.undoButtonWasPushed();
rc.undoButtonWasPushed();
rc.undoButtonWasPushed();
}
}
Output:
undoCommands stack is empty!
Outdoor light is on
Outdoor light is off
Outdoor light is on
Outdoor light is off
undoCommands stack is empty!
NoCommand
is know as Null Object which is another design pattern. It is useful when you don't have a meaning object to return. Here we use it as default command that Invoker
holds so that we don't need to handle null.
Pitfalls
- Client needs to do lots of things such as creating
ConcreteCommands
& setting correspondingReceivers
, creatingInvoker
& settingConcreteCommands
on thatInvoker
.
MacroCommand
MacroCommand is a simple extension of Command pattern that allows you to have one command that turns on a light, TV, AC, coffee machine and plays music.
Let's make a new kind of Command that executes a set of other Commands.
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
// Executes holding commands one at a time
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}
That's it! Now we're ready to use our MacroCommand. But before that, let me show you how to implement RemoteControl that can have multiple slots as I mentioned in "Solution" section.
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
public RemoteControl() {
// Our RemoteControl has 3 slots
onCommands = new Command[3];
offCommands = new Command[3];
Command noCommand = new NoCommand();
for (int i = 0; i < onCommands.length; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommands(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
// Print slots info in pretty style
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\n------ Remote Control ------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuilder.append("[slot " + i + "] " + onCommands[i].getClass().getSimpleName() +
" " + offCommands[i].getClass().getSimpleName() + "\n");
}
return stringBuilder.toString();
}
}
Pretty simple, just use array to store multiple commands.
Finally, Client uses our MacroCommand. For the sake of simplicity, I created only two receivers, Light
and CoffeeMachine
.
public class Client {
public static void main(String[] args) {
// Create receivers
Light light = new Light();
CoffeeMachine coffeeMachine = new CoffeeMachine();
// Create commands and set corresponding receivers on them
Command lightOnCommand = new LightOnCommand(light);
Command lightOffCommand = new LightOffCommand(light);
Command coffeeMachineOnCommand = new CoffeeMachineOn(coffeeMachine);
Command coffeeMachineOffCommand = new CoffeeMachineOff(coffeeMachine);
// Create arrays of commands
Command[] morningOn = {lightOnCommand, coffeeMachineOnCommand};
Command[] morningOff = {lightOffCommand, coffeeMachineOffCommand};
// Create macros
MacroCommand morningOnMacro = new MacroCommand(morningOn);
MacroCommand morningOffMacro = new MacroCommand(morningOff);
RemoteControl rc = new RemoteControl();
rc.setCommands(0, morningOnMacro, morningOffMacro);
System.out.println(rc);
System.out.println("--- Pushing Macro On ---");
rc.onButtonWasPushed(0);
System.out.println("\n--- Pushing Macro Off ---");
rc.offButtonWasPushed(0);
}
}
Output:
------ Remote Control ------
[slot 0] MacroCommand MacroCommand
[slot 1] NoCommand NoCommand
[slot 2] NoCommand NoCommand
--- Pushing Macro On ---
Light is on
Coffee machine is on
Coffee machine is making a cup of coffee
--- Pushing Macro Off ---
Light is off
Coffee machine is off
Our MacroCommand turns on the light and starts the coffee machine, which immediately begins brewing coffee. Note that we can define a set of actions in the Receiver and use them in the execute() method of the ConcreteCommand, as shown in the UML diagram in the "Structure" section.
@Override
public void execute() {
coffeeMachine.on();
coffeeMachine.makeCoffee();
}
You can check all the design pattern implementations here.
GitHub Repository
P.S.
I'm new to write tech blog, if you have advice to improve my writing, or have any confusing point, please leave a comment!
Thank you for reading :)
Top comments (0)