Command design pattern
There are situations where we invoke entities to do certain tasks for us. While we are clear on what to do, the “how” part of it is unknown, or rather irrelevant.
Take an example as simple as the “Copy” command in a text editor. Of course we know the action item here. But the details of how copying is done to clipboard is hidden from us. As long as we know the keyboard shortcut, or the correct option to choose from the drop down menu, or the correct option from the context menu, we are good.
Note that there are 2 points here- not only are the details of the (copy)command hidden from the user who invokes it, but also the command can also be invoked from multiple places, the invoker being a keyboard shortcut, a menu option, or a context menu. The task of copying however remains the same.
It then makes sense to encapsulate the user intent, of copying a text, into an entity. This entity can then be invoked from the multiple places. The intent is then conveyed to the actual workers, who work to convert the intent into a concrete action and result.
This is where the Command pattern comes into play. The intent, in the realm of Command pattern, is called the Command. The Command object decouples the entity which calls it- the Invoker, from the worker that realises the intent- the Receiver(as it receives the Command object). Observe that the decoupling happens on both sides of the Command object-on the Invoker’s side we can add a fourth place of invocation to the Copy command. On the Receiver’s side too, modifying the details of copying becomes easier. Also, once the Command object is created, it can be picked up by a Receiver later.
The Command object now being snugly encapsulated, can also be modified to be redone, or undone. Unintentionally copied a text? It can easily be undone. Want to log the texts copied in the last ’n’ number of copy commands? That text list is also maintained by the system.
The Command pattern is used in the UI buttons, in logging actions, in recovering a database after a failure by reading the logs and bringing it upto the ‘present’, and in many more places.
But to understand it in depth of how it is implemented, we will take an example which is closer to real life.
Let’e say you have an app, that lets you book various services- cleaning service, electrician service, painting service, plumbing service, salon service etc. When you book a service, say plumbing, you actually express your intent to get a plumbing job done. Behind the intent, there has to be an actual worker, a plumber, who knows how to do the job. There then needs to be 2 entities at this point, a Command capturing the intent, called PlumberCommand, and a Receiver called Plumber.
public class PlumberCommand implements Command{
Plumber plumber;
public PlumberCommand(Plumber plumberParam){
this.plumber=plumberParam;
}
@Override
public void execute() {
plumber.plumb();
}
}
public class Plumber {
public void plumb(){
System.out.println("Plumbing");
}
}
How do they interact? Well, the PlumberCommand has to contain a Plumber object, who actually carries out the job. The plumb() method in the Plumber contains all the intricacies to get the job done(replaced by a simple System.out.println for simplicity). The PlumberCommand only has to call this plumb() method, from its own execute() method.
On a closer look, PlumberCommand encapsulates the Plumber and its plumb() method. To the outside world, with an intent of getting a job done, the client only needs to call the execute() method of PlumberCommand.
Below we go through the classes that represent the outside world. We will go over the details of the Client and the App class.
领英推荐
public class Client {
public static void main(String[] args) {
CleaningCommand cleaningCommand=new CleaningCommand(new CleaningExpert());
ElectricianCommand electricianCommand=new ElectricianCommand(new Electrician());
PaintingCommand paintingCommand=new PaintingCommand(new Painter());
PlumberCommand plumberCommand=new PlumberCommand(new Plumber());
SalonCommand salonCommand=new SalonCommand(new SalonExpert());
App app=new App();
//Setting commands to various indices
app.setTasks(0,cleaningCommand);
app.setTasks(1,electricianCommand);
app.setTasks(2,paintingCommand);
app.setTasks(3,plumberCommand);
app.setTasks(4,salonCommand);
app.callExecute(0);
app.callExecute(1);
app.callExecute(2);
app.callExecute(3);
app.callExecute(4);
}//main
}
public class App {
Command[] buttons;
public App(){
buttons=new Command[5];
}
public void setTasks(int buttonIndex, Command command){
buttons[buttonIndex]=command;
}
public void callExecute(int buttonIndex){
Command commandToExecute=buttons[buttonIndex];
commandToExecute.execute();
}
}
Let’s take a look at the App class first. Think of it as a web or mobile application, which aggregates various services. For simplicity, I have added the code of adding Command objects to button indices in the setTask() method of this class itself. Ideally, the method will be in a separate class. This is where each of the Commands are assigned to specific UI buttons, so that the user knows which button to press for which task. Once tasks are set, each button will be holding a reference to a separate Command object?. Remember the PlumberCommand above? We create similar Command objects for the other services, and set them to different UI buttons(buttonIndex).
With that done, we now focus on the callExecute() method. This method takes in the buttonIndex as a param, and with that it knows which command to execute(as at this point setTasks() is already called). Through the commandToExecute, it calls the execute() method of that Command. At runtime, the commandToExecute can be a PlumberCommand, PainterCommand, or any other command. We already saw the flow after the execute method is called, where the execute() method calls the specific method of the Receiver object(the Plumber object for instance).
Note that during setTasks(), the commands are only stored until needed. Only at some future point, the callExecute() method?, which calls the execute() method is called.
So at runtime, the Client class calls the callExecute() methods of the App class. Think of the Client class as a real user, who selects a service in the app by calling the callExecute() of the relevant service(buttonIndex).
This is the whole flow. The Client calls the callExecute() method of the App(Invoker), which accesses the execute() method of the actual Command object(PlumberCommand here). Away from the outer world, this Command object then calls the method of the right Receiver object(Plumber’s plumb() method in this case), which carries out the task.
The Command object will take requests, and hands it to the correct Receiver. Note that the Command object knows who is its correct Receiver, and calls that Receiver’s method inside its own execute() method. The outside world(Invoker) only has to call the execute() method of the Command.
Definition
The command pattern encapsulates a request as an object(PlumberObject here), queues Command requests(to be picked up by Receivers later), logs requests, and also supports undoing operations.