Introduction
You most likely have known about behavioural patterns by now. behavioral patterns are concerns about the wiring of Java objects. While there are around 12 design patterns that belongs to behavioral patterns, the command patters takes its special place as it is utilized frequently than other patterns. The motivation behind the order design is to decouple the rationale amongst commands and its consumers.
The key part of the command pattern is encapsulating all the data related to command in one object. Usually, this is done by a set of methods, their parameters and one or more objects to which these methods belong to called as Receiver. So the important point about decoupling is if you had to change any of these values, you only have to change one class.
In the classic version, implementing the command pattern involves five steps.
- The Command interface: This is usually declared just a single method for executing the command.
- The ConcreteCommand: This is an operation with parameters that pass the call to the receiver; In the classic approach, a command only invokes one or more methods of a Receiver rather than perform business logic.
- The Receiver: is especially knows how to perform the action.
- The Invoker: asks the command to carry out the request.
- The Client: creates a ConcreteCommand object and sets the Receiver.
Example of the command pattern
Suppose you are going to build a home automation system where you need to turn on and off a Heating system. Both the commands are similar in most part of the sense. We could create one interface as Command
and It will have only one method called execute()
.
public interface Command {
void execute();
}
We will now create two classes that will implement the Command
interface. These concrete classes encapsulate data required for the command to execute two commands, Heating On and Heating Off.
First. HeatingOnCommand
will implement the Command
interface.
public class HeatingOnCommand implements Command {
private HeatingSystem heatingSystem;
public HeatingOnCommand(HeatingSystem heatingSystem) {
this.heatingSystem = heatingSystem;
}
@Override
public void execute() {
heatingSystem.heatOn();
}
}
Next, HeatingOffCommand
will implement the Command
interface. HeatingOffCommand
basically has the same code that HeatingOnCommand
has.
public class HeatingOffCommand implements Command {
private HeatingSystem heatingSystem;
public HeatingOffCommand(HeatingSystem heatingSystem) {
this.heatingSystem = heatingSystem;
}
@Override
public void execute() {
heatingSystem.heatOff();
}
}
Next, we will create our Receiver class which is here HeatingSystem
class
public class HeatingSystem {
public void heatOn() {
System.out.println("Turn on heat");
}
public void heatOff() {
System.out.println("Turn off heat");
}
}
Next, we need to require the Invoker class. invoker class decides how the commands are executed. For example, the invoker can keep a list of commands that need to be executed in a specific order.
We will name the invoker class as Controller
here.
public class Controller {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
Finally, our client or the main
method will use the invoker to execute the command.
public class HomeHeatingSystemAutomation {
public static void main(String[] args) {
Controller controller = new Controller();
HeatingSystem heatingSystem = new HeatingSystem();
Command heatOn = new HeatingOnCommand(light);
Command heatOff = new HeatingOffCommand(light);
controller.setCommand(heatOn);
controller.executeCommand();
controller.setCommand(heatOff);
controller.executeCommand();
}
}
Basically, there are three significant steps in the main method.
- Creating an object from the invoker class which is
Controller
in our application. - Creating objects from commands that we are going to execute.
- Executing commands using invokers.
There could be other steps that are needed to support these three main steps. For example, This main()
method has created a HeatingSystem
object because a HeatingSystem
object is needed to pass to create Command
objects. When you execute this code, the following output will be produced.
Additional options
The Command pattern can be used together with the following options:
- adding commands to a queue to execute them later;
- supporting undo/redo operations;
- storing a history of commands;
- serializing commands to store them on a disk;
These options are not essential to the pattern but are often used in practice.
Conclusion
The main advantage of the command pattern is that it decouples the object that invokes the operation from the one that knows how to perform it. Various modifications of this pattern can be used to keep a history of requests, implement the undo functionality and create macro commands. However, application can become more complicated because this pattern as in adds another layer of abstraction.
Top comments (1)
Where did this light came from? We need to put the HeatingSystem in constructor, but u are doing this:
HeatingSystem heatingSystem = new HeatingSystem();
and then putting light HeatingSystem out of nowhere
Command heatOn = new HeatingOnCommand(light);
Command heatOff = new HeatingOffCommand(light);