© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_17

17. Command Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

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

Figure 17-1 shows the class diagram.
../images/395506_2_En_17_Chapter/395506_2_En_17_Fig1_HTML.jpg
Figure 17-1

Class diagram

Package Explorer View

Figure 17-2 shows the high-level structure of the program.
../images/395506_2_En_17_Chapter/395506_2_En_17_Fig2_HTML.jpg
Figure 17-2

Package Explorer view

Implementation

Here’s the implementation.
package jdp2e.command.demo;
interface Command
{
    //Typically this method does not take any argument.
    //Some of the reasons are:
    //1.We supply all the information when it is created.
    //2.Invoker may reside in different address space.etc.
    void executeCommand();
}
class MyUndoCommand implements Command
{
    private Receiver receiver;
    public MyUndoCommand(Receiver receiver)
    {
        this.receiver=receiver;
    }
    @Override
    public void executeCommand()
    {
        //Perform any optional task prior to UnDo
        receiver.doOptionalTaskPriorToUndo();
        //Call UnDo in receiver now
        receiver.performUndo();
    }
}
class MyRedoCommand implements Command
{
    private Receiver receiver;
    public MyRedoCommand(Receiver receiver)
    {
        this.receiver=receiver;
    }
    @Override
    public void executeCommand()
    {
        //Perform any optional task prior to ReDo
        receiver.doOptionalTaskPriorToRedo();
        //Call ReDo in receiver now
        receiver.performRedo();
    }
}
//Receiver Class
class Receiver
{
    public void performUndo()
    {
        System.out.println("Performing an undo command in Receiver.");
    }
    public void performRedo()
    {
        System.out.println("Performing an redo command in Receiver.");
    }
    /*Optional method-If you want to perform
     any prior tasks before undo operations.*/
    public void doOptionalTaskPriorToUndo()
    {
        System.out.println("Executing -Optional Task/s prior to    execute undo command.");
    }
    /*Optional method-If you want to perform
     any prior tasks before redo operations*/
    public void doOptionalTaskPriorToRedo()
    {
        System.out.println("Executing -Optional Task/s prior to    execute redo command.");
    }
}
//Invoker class
class Invoker
{
    Command commandToBePerformed;
    //Alternative approach:
    //You can also initialize the invoker with a command object
    /*public Invoker(Command command)
    {
        this.commandToBePerformed = command;
    }*/
    //Set the command
    public void setCommand(Command command)
    {
        this.commandToBePerformed = command;
    }
    //Invoke the command
    public void invokeCommand()
    {
        commandToBePerformed.executeCommand();
    }
}
//Client
public class CommandPatternExample {
    public static void main(String[] args) {
        System.out.println("***Command Pattern Demo*** ");
        /*Client holds both the Invoker and Command Objects*/
        Receiver intendedReceiver = new Receiver();
        MyUndoCommand undoCmd = new MyUndoCommand(intendedReceiver);
        //If you use parameterized constructor of Invoker
        //use the following line of code.
        //Invoker invoker = new Invoker(undoCmd);
        Invoker invoker = new Invoker();
        invoker.setCommand(undoCmd);
        invoker.invokeCommand();
        MyRedoCommand redoCmd = new MyRedoCommand(intendedReceiver);
        invoker.setCommand(redoCmd);
        invoker.invokeCommand();
    }
}

Output

Here’s the output.
***Command Pattern Demo***
Executing -Optional Task/s prior to    execute undo command.
Performing an undo command in Receiver.
Executing -Optional Task/s prior to    execute redo command.
Performing an redo command in Receiver.

Q&A Session

  1. 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?

     
Consider the following program. The key characteristics of this program are as follows:
  • 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

There are many participants and dependencies in the modified class diagram shown in Figure 17-3. To illustrate the main design and keep the diagram neat and clean, I do not show the client code dependencies.
../images/395506_2_En_17_Chapter/395506_2_En_17_Fig3_HTML.jpg
Figure 17-3

Modified class diagram

Modified Package Explorer View

Figure 17-4 shows the modified Package Explorer view.
../images/395506_2_En_17_Chapter/395506_2_En_17_Fig4_HTML.jpg
Figure 17-4

Modified Package Explorer view

Modified Implementation

Here’s the modified implementation
package jdp2e.command.modified.demo;
/**
 *In general, an undo operation involves complex logic.
 But for simplicity, in this example,I assume that executeDo() can either add 2 with a given integer or it can switch on a machine.
 Similarly, executeUnDo() can either subtract 2 from a given number() or,
 it will switch off a machine.But you cannot go beyond the initialized value(i.e.10 in this case)*/
interface Command
{
    void executeDo();
    void executeUnDo();
}
class AdditionCommand implements Command
{
    private Receiver receiver;
    public AdditionCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }
    @Override
    public void executeDo()
    {
        receiver.performDo();
    }
    @Override
    public void executeUnDo()
    {
        receiver.performUnDo();
    }
}
class PowerCommand implements Command
{
    private Receiver receiver;
    public PowerCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }
    @Override
    public void executeDo()
    {
        receiver.performDo();
    }
    @Override
    public void executeUnDo()
    {
        receiver.performUnDo();
    }
}
//To deal with multiple receivers , we are using interfaces here
interface Receiver
{
    //It will add 2 with a number or switch on the m/c
    void performDo();
    //It will subtract 2 from a number or switch off the m/c
    void performUnDo();
}
//Receiver Class
class Receiver1 implements Receiver
{
    private int myNumber;
    public int getMyNumber()
    {
        return myNumber;
    }
    public void setMyNumber(int myNumber)
    {
        this.myNumber = myNumber;
    }
    public Receiver1()
    {
        myNumber = 10;
        System.out.println("Receiver1 initialized with " + myNumber);
        System.out.println("The objects of receiver1 cannot set beyond "+ myNumber);
    }
    @Override
    public void performDo()
    {
        System.out.println("Received an addition request.");
        int presentNumber = getMyNumber();
        setMyNumber(presentNumber + 2);
        System.out.println(presentNumber +" + 2 ="+ this.myNumber);
    }
    @Override
    public void performUnDo()
    {
        System.out.println("Received an undo addition request.");
        int presentNumber = this.myNumber;
        //We started with number 10.We'll not decrease further.
        if (presentNumber > 10)
        {
            setMyNumber(this.myNumber - 2);
            System.out.println(presentNumber +" - 2 ="+ this.myNumber);
            System.out.println(" Undo request processed.");
        }
        else
        {
            System.out.println("Nothing more to undo...");
        }
    }
}
//Receiver2 Class
class Receiver2 implements Receiver
{
    boolean status;
    public Receiver2()
    {
        System.out.println("Receiver2 initialized ");
        status=false;
    }
    @Override
    public void performDo()
    {
        System.out.println("Received a system power on request.");
        if( status==false)
        {
            System.out.println("System is starting up.");
            status=true;
        }
        else
        {
            System.out.println("System is already running.So, power on request is ignored.");
        }
    }
    @Override
    public void performUnDo()
    {
        System.out.println("Received a undo request.");
        if( status==true)
        {
            System.out.println("System is currently powered on.");
            status=false;
            System.out.println(" Undo request processed.System is switched off now.");
        }
        else
        {
            System.out.println("System is switched off at present.");
            status=true;
            System.out.println(" Undo request processed.System is powered on now.");
        }
    }
}
//Invoker class
class Invoker
{
    Command commandToBePerformed;
    public void setCommand(Command command)
    {
        this.commandToBePerformed = command;
    }
    public void executeCommand()
    {
        commandToBePerformed.executeDo();
    }
    public void undoCommand()
    {
        commandToBePerformed.executeUnDo();
    }
}
//Client
public class ModifiedCommandPatternExample {
    public static void main(String[] args) {
        System.out.println("***Command Pattern Q&As***");
        System.out.println("***A simple demo with undo supported operations*** ");
        //Client holds  both the Invoker and Command Objects
        //Testing receiver -Receiver1
        System.out.println("-----Testing operations in Receiver1-----");
        Receiver intendedreceiver = new Receiver1();
        Command currentCmd = new AdditionCommand(intendedreceiver);
        Invoker invoker = new Invoker();
        invoker.setCommand(currentCmd);
        System.out.println("*Testing single do/undo operation*");
        invoker.executeCommand();
        invoker.undoCommand();
        System.out.println("_______");
        System.out.println("**Testing a series of do/undo operations**");
        //Executed the command 2 times
        invoker.executeCommand();
        //invoker.undoCommand();
        invoker.executeCommand();
        //Trying to undo 3 times
        invoker.undoCommand();
        invoker.undoCommand();
        invoker.undoCommand();
        System.out.println(" -----Testing operations in Receiver2-----");
        intendedreceiver = new Receiver2();
        currentCmd = new PowerCommand(intendedreceiver);
        invoker.setCommand(currentCmd);
        System.out.println("*Testing single do/undo operation*");
        invoker.executeCommand();
        invoker.undoCommand();
        System.out.println("_______");
        System.out.println("**Testing a series of do/undo operations**");
        //Executing the command 2 times
        invoker.executeCommand();
        invoker.executeCommand();
        //Trying to undo 3 times
        invoker.undoCommand();
        invoker.undoCommand();
        invoker.undoCommand();
    }
}

Modified Output

Here’s the modified output.
***Command Pattern Q&As***
***A simple demo with undo supported operations***
-----Testing operations in Receiver1-----
Receiver1 initialized with 10
The objects of receiver1 cannot set beyond 10
*Testing single do/undo operation*
Received an addition request.
10 + 2 =12
Received an undo addition request.
12 - 2 =10
     Undo request processed.
_______
**Testing a series of do/undo operations**
Received an addition request.
10 + 2 =12
Received an addition request.
12 + 2 =14
Received an undo addition request.
14 - 2 =12
     Undo request processed.
Received an undo addition request.
12 - 2 =10
     Undo request processed.
Received an undo addition request.
Nothing more to undo...
-----Testing operations in Receiver2-----
Receiver2 initialized
*Testing single do/undo operation*
Received a system power on request.
System is starting up.
Received a undo request.
System is currently powered on.
     Undo request processed.System is switched off now.
_______
**Testing a series of do/undo operations**
Received a system power on request.
System is starting up.
Received a system power on request.
System is already running.So, power on request is ignored.
Received a undo request.
System is currently powered on.
     Undo request processed.System is switched off now.
Received a undo request.
System is switched off at present.
     Undo request processed.System is powered on now.
Received a undo request.
System is currently powered on.
     Undo request processed.System is switched off now .
  1. 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.

     
  2. 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).

     
  3. 4.

    Why are you interested in keeping track of these logs?

    They are useful if you want to do the undo or redo operations.

     
  4. 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.

     
  5. 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.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.135.225