Command pattern
Created on: Sep 29, 2024
The Command Pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support unable operations.
Suppose we are creating a home controller device in which we have a remote control which can control light, fan, Garage door, thermostat in a house or any other thing that can come. Our purpose is to write code in such a way that remote device does not know about the working of any device mentioned. It only knows how to start the device or execute any command.
Let's start by adding Light and thermostat device. Below is a sample code for these device. In the real world scenario, this will be a well defined api which remote device can access.
public class Light { private String name; public Light(String name) { this.name = name; } public void onLight(){ System.out.println(name+" light is on"); } public void offLight(){ System.out.println(name+" light is off"); } } public class Thermostat { public void on(){ System.out.println("Thermostat is on"); } public void off(){ System.out.println("Thermostat is off"); } }
Now let's create a generic command which any device can implement for its action. And few of its implementation.
public interface Command { public void execute(); public void undo(); } public class LightonCommand implements Command { private final Light light; public LightonCommand(Light light) { this.light = light; } @Override public void execute() { this.light.onLight(); } @Override public void undo() { this.light.offLight(); } } public class LightOffCommand implements Command { private final Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { this.light.offLight(); } @Override public void undo() { this.light.onLight(); } } public class ThermostatOffCommand implements Command { private final Thermostat thermostat; public ThermostatOffCommand(Thermostat thermostat) { this.thermostat = thermostat; } @Override public void execute() { this.thermostat.off(); } @Override public void undo() { this.thermostat.on(); } } public class ThermostatOnCommand implements Command { private final Thermostat thermostat; public ThermostatOnCommand(Thermostat thermostat) { this.thermostat = thermostat; } @Override public void execute() { this.thermostat.on(); } @Override public void undo() { this.thermostat.off(); } }
Below is the code for remote device which can have a list of those command.
public class Remote { Map<CommandName, Command> commands = new HashMap(); public Remote(Map<CommandName, Command> commands) { this.commands = commands; } public void addCommand(CommandName commandName, Command onCommand) { this.commands.put(commandName, onCommand); } public void executeCommand(CommandName commandName){ Command command = this.commands.get(commandName); if(Objects.isNull(command)){ throw new RuntimeException("Command not found"); } command.execute(); } }
And below is the demo which can switch on and off light and thermostat.
public class CommandPatternDemo { public static void main(String[] args) { Map<CommandName, Command> map = new HashMap<>(); Light beedroomLight = new Light("Beedroom light"); Thermostat thermostat = new Thermostat(); map.put(CommandName.BEDROOM_LIGHT_ON, new LightonCommand(beedroomLight)); map.put(CommandName.BEDROOM_LIGHT_OFF, new LightOffCommand(beedroomLight)); map.put(CommandName.THERMOSTAT_OFF, new ThermostatOnCommand(thermostat)); map.put(CommandName.THERMOSTAT_ON, new ThermostatOffCommand(thermostat)); Remote remote = new Remote(map); remote.executeCommand(CommandName.BEDROOM_LIGHT_ON); remote.undoCommand(CommandName.BEDROOM_LIGHT_ON); remote.executeCommand(CommandName.THERMOSTAT_ON); } }
Beedroom light light is on Beedroom light light is off Thermostat is off
Let's suppose we have added kitchen light under remote control. Below modification will work for this.
Light kitchenLight = new Light("Kitchen light"); remote.addCommand(CommandName.KITCHEN_LIGHT_ON, new LightonCommand(kitchenLight)); remote.addCommand(CommandName.KITCHEN_LIGHT_OFF, new LightOffCommand(kitchenLight)); remote.executeCommand(CommandName.KITCHEN_LIGHT_ON);
Command pattern solves following problem.
- Maintenance & Flexibility: Since receiver and invoker are decoupled, change in received logic does not affect invoker.
- Open/Closed Principle: We can add more action/command instances to system without changing invoker code.
- Undo: Undo command is encapsulated inside command object which make it easy for tracking and do reverse action.
Real world use case
- Command pattern can be used in multithreading. In below program Runnable task is the Command and ExecutorService is invoker.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Task implements Runnable{ String name; public Task(String name) { this.name = name; } @Override public void run() { System.out.println("Executing task "+name); } } public class Test { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); Runnable task1 = new Task("task1"); Runnable task2 = new Task("task2"); executorService.submit(task1); executorService.submit(task2); executorService.shutdown(); } }
- Most of modern text editor used command pattern for undo and redo functionality.
- In spring batch, each job is like like a command. Each steps like reading, processing and writing data are independent command that can be executed in sequence.
Cases where we can use command pattern:
- Transactional Systems: In transaction there are multiple series of commands. We can use command pattern to group multiple command together and execute. If any of them fails we can rollback using undo method of each method.
- Logging: The Command Pattern can be used to implement logging by recording the execution of each command.
Check full code in github
