This chapter covers the command pattern.
GoF Definition
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queues, or log requests, and supports undoable operations.
Concept
Here you encapsulate a method invocation process. In general, four terms are associated: invoker, client, command, and receiver. A command object can invoke a method of the receiver in a way that is specific to that receiver’s class. The receiver then starts processing the job. A command object is separately passed to the invoker object to invoke the command. The client object holds the invoker object and the command objects. The client only makes the decision—which commands to execute—and then it passes the command to the invoker object (for that execution).
Real-World Example
When you draw something with a pencil, you may need to undo (erase and redraw) some parts to make it better.
Computer-World Example
The real-world scenario for painting applies to Microsoft Paint. You can use the Menu or Shortcut keys to perform the undo/redo operations in those contexts.
In general, you can observe this pattern in the menu system of an editor or IDE (integrated development environment). So, if you want to make an application that needs to support undos, multiple undos, or similar operations, then the command pattern can be your savior.
Microsoft used this pattern in Windows Presentation Foundation (WPF). The online source at https://visualstudiomagazine.com/articles/2012/04/10/command-pattern-in-net.aspx describes it in detail: “The command pattern is well suited for handling GUI interactions. It works so well that Microsoft has integrated it tightly into the Windows Presentation Foundation (WPF) stack. The most important piece is the ICommand interface from the System. Windows.Input namespace. Any class that implements the ICommand interface can be used to handle a keyboard or mouse event through the common WPF controls. This linking can be done either in XAML or in a code-behind.”
Note
When you implement the run() method of java.lang.Runnable interface , you are basically using the command design pattern. Another interface, java.swing.Action, also represents the command design pattern. It is important to note that the implementation of undos varies and can be complex. The memento design pattern also supports undo operations. You may need to use both of these design patterns in your application to implement a complex undo operation.
Illustration
Consider the following example. For an easy understanding, I am following similar class names to the concept described earlier. You can refer to the associated comments for a better understanding.
Class Diagram
Package Explorer View
Implementation
Output
Q&A Session
- 1.
I have two questions. In this example, you are dealing with a single receiver only. How can you deal with multiple receivers? And the GoF definition says that this pattern supports undoable operations. Can you show an example with a true undo operation using this pattern?
Here you have two different receivers (Receiver1 and Receiver2). Each of them implements the Receiver interface methods. Since I am dealing with multiple receivers, I introduced a common interface, Receiver.
- In an undo operation, you generally want to reverse the last action or operation. A typical undo operation may involve complex logic. But in the upcoming implementation, I am presenting a simple example that supports undo operations with the following assumptions.
A Receiver1 object is initialized with the value 10 (the myNumber instance variable is used for this purpose) and a Receiver2 object is initialized with the “power off” status (the status instance variable is used for this purpose). Any Receiver1 object can keep adding 2 to an existing integer.
I have put a checkmark on the value 10, so that when you process an undo operation, if you notice that a Receiver1 object’s myNumber is 10, you will not go beyond (because you started at 10).
A Receiver2 object does different things. It can switch a machine on or off. If the machine is already powered on, then by requesting an undo operation, you can switch off the machine and vice versa. But if your machine is already in switch on mode, then a further “switch on” request is ignored.
Modified Class Diagram
Modified Package Explorer View
Modified Implementation
Modified Output
- 2.
In this modified program, two receivers are doing different things. Is this intentional?
Yes. It shows the power and flexibilities provided by the command design pattern. You can see that performDo() in these receivers actually performs different actions. For Receiver1, it is adding 2 with an existing integer, and for Receiver2, it is switching on a machine. So, you may think that some other names like addNumber() and powerOn() would be more appropriate for them.
But in this case, I needed to work with both the receivers and their corresponding methods. So, I needed to use a common interface and common names that could be used by both receivers.
So, if you need to work with two different receivers that have different method names, you can replace them with a common name, use a common interface, and through polymorphism, you can invoke those methods easily.
- 3.
Why do you need the invoker?
Most of the time, programmers try to encapsulate data and corresponding methods in object-oriented programming. But if you look carefully, you find that in this pattern, you are trying to encapsulate command objects. In other words, you are implementing encapsulation from a different perspective.
This approach makes sense when you deal with a complex set of commands.
Now let’s review the terms again. You create command objects to shoot them to receivers and invoke some methods. But you execute those commands through an invoker, which calls the methods of the command object (e.g., executeCommand). But for a simple case, this invoker class is not mandatory; for example, consider a case in which a command object has only one method to execute and you are trying to dispense with the invoker to invoke the method. But the invokers may play an important role when you want to keep track of multiple commands in a log file (or in a queue).
- 4.
Why are you interested in keeping track of these logs?
They are useful if you want to do the undo or redo operations.
- 5.What are the key advantages associated with command patterns?
Requests for creation and the ultimate execution are decoupled. Clients may not know how an invoker is performing the operations.
You can create macros (sequence of commands).
New commands can be added without affecting the existing system.
Most importantly, you can support the undo/redo operations.
- 6.What are the challenges associated with command patterns?
To support more commands, you need to create more classes. So, maintenance can be difficult as time goes on.
How to handle errors or make a decision about what to do with return values when an erroneous situation occurs becomes tricky. A client may want to know about those. But here you decouple the command with client codes, so these situations are difficult to handle. The challenge becomes significant in a multithreaded environment where the invoker is also running in a different thread.