Command mode – let the program execute smoothly

● Introduction to command mode

Command Pattern is one of the behavioral design patterns. Compared with other design patterns, the command pattern does not have so many rules and regulations. In fact, it is not a very “ruled” pattern. However, based on one point, the command pattern is more flexible and changeable than other design patterns. The command mode examples that we come into contact with more often are nothing more than program menu commands. For example, in the operating system, when we click the “Shutdown” command, the system will perform a series of operations, such as first pausing event processing, saving some system configurations, and then End the program process, and finally call the kernel command to shut down the computer. For this series of commands, the user does not need to worry about it. The user only needs to click the system shutdown button to complete the above series of commands. Our command mode is actually the same. It encapsulates a series of method calls. The user only needs to call one method to execute, and then all these encapsulated methods will be called one by one.

● Definition of command mode

Encapsulate a request into an object, allowing users to parameterize the client with different requests; queue or log requests, and support reversible operations.

● Usage scenarios of command mode

The action to be performed needs to be abstracted and then provided in the form of parameters – similar to the callback mechanism in process design, and the command pattern is an object-oriented alternative to the callback mechanism.

Specify, queue and execute requests at different times. A command object can have a lifetime independent of the initial request.

Need to support cancellation operation.

Supports the modification log function so that when the system crashes, these modifications can be redone.

Need to support transaction operations.

● UML class diagram of command mode

The UML class diagram is shown in the figure below.

According to the class diagram, the following general pattern code of the command pattern can be derived.

Receiver class

/**
 * Receiver class
 */
public class Receiver {

    /**
     * A method to actually execute specific command logic
     */
    public void action() {
        System.out.println("Perform specific operations");
    }
}

abstract named interface

/**
 * Abstract named interface
 */
public interface Command {
    /**
     * Execute specific operation commands
     */
    void execute();
}

Specific command class

/**
 * Specific command class
 */
public class ConcreteCommand implements Command {
    private Receiver receiver;//Holds a reference to the receiver object

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;

    }

    @Override
    public void execute() {
        //Call the relevant methods of the receiver to execute specific logic
        receiver.action();
    }
}

Requester class

/**
 * Requester class
 */
public class Invoker {
    private Command command;//Holds a reference to the corresponding command object

    public Invoker(Command command) {
        this.command = command;
    }

    public void action() {
        //Call the relevant methods of the specific command object to execute the specific command
        command.execute();
    }
}

Customer type

/**
 *Customer category
 */
public class Client {
    public static void main(String[] args) {
        //Construct a receiver object
        Receiver receiver = new Receiver();

        //Construct a command object based on the receiver object
        Command command = new ConcreteCommand(receiver);

        //Construct the requester object based on the specific object
        Invoker invoker = new Invoker(command);

        //Execute request method
        invoker.action();
    }
}

Character introduction.

Receiver: Receiver role.

This type of complex specifically implements or executes a request. To put it simply, it is the role that executes specific logic. Taking the “shutdown” command operation at the beginning as an example, the role of the recipient is the underlying code that actually executes various shutdown logics. Any class can become a receiver, and the method that encapsulates specific operation logic in the receiver class is called a behavioral method.

Command: command role.

Defines the abstract interface for all concrete command classes.

ConcreteCommand: Concrete command role.

This class implements the Command interface, calls the relevant methods of the recipient role in the execute method, and provides physical coupling between the recipient and the specific behavior of command execution. execute is usually called the execution method. As shown at the beginning of this article, the “shutdown” operation is implemented. It may also include many related operations, such as saving data, closing files, ending processes, etc. If this series of specific logic processing As a receiver, the method of calling these specific logic can be regarded as an execution method.

Invoker: Requester role

The responsibility of this class is to call the command object to execute specific requests. The related methods are called action methods. Let’s use “Shutdown” as an example. The “Shutdown” menu command generally corresponds to a shutdown method. After we click the “Shutdown” command , this shutdown method calls specific commands to execute specific logic. The method corresponding to “shutdown” here can be regarded as the requester.

Client: client role

Taking the example of “shutdown” as a human being, it is easy to understand and I won’t say more.

In fact, you can see here that the application of the command mode can actually be summarized in one sentence, which is to decouple the behavior caller from the implementer. As a simple example, we often call code like this.

 Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setShader(new SweepGradient(canvas.getWidth() / 2
                , canvas.getHeight() / 2
                , new int[]{0xffeeadde, 0xffbc4579, 0xffe9c43f, 0xffdccb48, 0xff44adde}
                , null));

This kind of logical call is very familiar. First, new a Paint object, and then call the setShader method in Paint to set a shader for Paint. However, if our calling logic is complex or there are multiple implementations of the calling behavior, this way of coupling the behavior caller and the behavior implementer will be very problematic. At this time, it is very necessary to use design patterns to decouple. Of course, this is just an introduction for everyone to think about, without further analysis.

● Simple implementation of command mode

The command pattern is not difficult in general, but it is relatively cumbersome. If you think about a simple calling relationship being decoupled into multiple parts, it will definitely increase the complexity of the class, but even so, the structure of the command pattern is still clear. Everyone should have played the Tetris game when they were children. Here we take the ancient Tetris game as an example to see how we control the changes of Tetris in command mode. Generally speaking, there are 4 buttons in the Tetris game, two devices for moving left and right, a button for falling quickly, and a button for changing the shape of the blocks. This is a classic game prototype in computing. A person playing the game is equivalent to our client, and the four buttons on the game are equivalent to the requester, or can also be called the caller. The logical method of executing specific button commands can be regarded as the command role. Of course, the game We don’t know how it is implemented internally, and we will not discuss it here. We will only analyze it with examples. In the end, it is the game itself that actually executes and processes the specific logic. You can think of it as the specific logic executed by various machine code calculations and processing. Here We think of it as a receiver role. The logical analysis is relatively clear, let’s “translate” it into code, first of all, our receiver, here the Tetris game itself is used as the receiver role.

Receiver role, Tetris game.

/**
 * Receiver role Tetris game
 */
public class TetrisMachine {
    /**
     * The logic code that actually handles the "left" operation
     */
    public void toLeft() {
        System.out.println("To the left");
    }

    /**
     * The logic code that is processing the "right" operation
     */
    public void toEight() {
        System.out.println("right");
    }

    /**
     * Logic code for normal processing of "rapid falling" operation
     */
    public void fastToBottom() {
        System.out.println("Fast falling");
    }

    /**
     * The logic code that actually handles the "change shape" operation
     */
    public void transform() {
        System.out.println("Change shape");
    }
}

The TetrisMachine class is the only place in the entire command mode that handles specific code logic. Other classes directly or indirectly call the corresponding methods. This is the role of the receiver, which handles specific logic. As we said above, the receiver class is just an ordinary class, and any class can be used as a receiver. Next we define an interface as an abstraction of the command role.

The commander abstracts and defines the execution method.

/**
 *Commander abstraction defines execution method
 */
public interface Command {
    /**
     * Command execution
     */
    void execute();
}

Then there are 4 specific commands: move left, move right, drop and transform.

Specific commands, command classes that move to the left.

/**
 * Specific command: command class to move to the left
 */
public class LeftCommand implements Command {
    //Hold a reference to the receiver Tetris game object
    private TetrisMachine machine;

    public LeftCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        //Call specific methods in the game console to perform operations
        machine.toLeft();
    }
}

Specific commander, command class that moves to the right.

/**
 * Specific commander, command class moved to the right
 */
public class RigthCommand implements Command {
    //Hold a reference to the receiver Tetris game object
    private TetrisMachine machine;

    public RigthCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        //Call specific methods in the game console to perform operations
        machine.toRight();
    }
}

Specific commander, fast falling command class.

/**
 * Specific commander, fast falling command type.
 */
public class FallCommand implements Command {
    //Hold a reference to the receiver Tetris game object
    private TetrisMachine machine;

    public FallCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        //Call specific methods in the game console to perform operations
        machine.fastToBottom();
    }
}

Concrete commander, a command class that changes the shape.

/**
 * Specific commander, a command class that changes the shape.
 */
public class TransformCommand implements Command {
    //Hold a reference to the receiver Tetris game object
    private TetrisMachine machine;

    public TransformCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        //Call specific methods in the game console to perform operations
        machine.transform();
    }
}

As you can see from the program, the method names in the commander role class and the method names in the TetrisMachine receiver role class can be different, and there is only a weak coupling between the two. For the requester, here we represent it with a Buttons class, and the commands are executed by buttons.

Requester class, named by button.

/**
 * Requester class, naming is initiated by button.
 */
public class Buttons {
    private LeftCommand leftCommand;//Command object reference for moving left
    private RightCommand rightCommand;//Command object reference for moving to the right
    private FallCommand fallCommand;//Quickly falling command object reference
    private TransformCommand transformCommand;//Command object reference for transforming shape

    /**
     * Set the command object to move left
     *
     * @param leftCommand command object to move to the left
     */
    public void setLeftCommand(LeftCommand leftCommand) {
        this.leftCommand = leftCommand;
    }

    /**
     * Set the command object to move to the right
     *
     * @param rightCommand command object to move to the right
     */
    public void setRightCommand(RightCommand rightCommand) {
        this.rightCommand = rightCommand;
    }

    /**
     * Set the command object for rapid falling
     *
     * @param fallCommand fast falling command object
     */
    public void setFallCommand(FallCommand fallCommand) {
        this.fallCommand = fallCommand;
    }

    /**
     * Set the command object of the transformation shape
     *
     * @param transformCommand The command object to transform the shape
     */
    public void setTransformCommand(TransformCommand transformCommand) {
        this.transformCommand = transformCommand;
    }

    /**
     * Press button to move left
     */
    public void toLeft() {
        leftCommand.execute();
    }

    /**
     * Press button to move right
     */
    public void toRight() {
        rightCommand.execute();
    }

    /**
     * Press the button to fall quickly
     */
    public void fall() {
        fallCommand.execute();
    }

    /**
     * Press button to change state
     */
    public void transform() {
        transformCommand.execute();
    }
}

Customer type

/**
 *Customer category
 */
public class Player {
    public static void main(String[] args) {
        //First there must be a Tetris game
        TetrisMachine machine = new TetrisMachine();

        //We construct 4 kinds of commands according to the game
        LeftCommand leftCommand = new LeftCommand(machine);
        RightCommand rightCommand = new RightCommand(machine);
        FallCommand fallCommand = new FallCommand(machine);
        TransformCommand transformCommand = new TransformCommand(machine);

        //Buttons can execute different commands
        Buttons buttons = new Buttons();
        buttons.setLeftCommand(leftCommand);
        buttons.setRightCommand(rightCommand);
        buttons.setFallCommand(fallCommand);
        buttons.setTransformCommand(transformCommand);

        //The toy has the final say which button to press.
        buttons.toLeft();
        buttons.toRight();
        buttons.fall();
        buttons.transform();
    }
}

Maybe you have doubts after reading this long code. It is obviously a simple logic, why is it so complicated? Most developers prefer to explain the code as follows.

/**
 *Customer category
 */
public class Player {
    public static void main(String[] args) {
        //First there must be a Tetris game
        TetrisMachine machine = new TetrisMachine();

        //What kind of control method we want to implement, we just call the relevant function directly.
        machine.toLeft();
        machine.toRight();
        machine.fastToBottom();
        machine.transform();
    }
}

The calling logic is so complicated because it is convenient to call. Every time we add or modify the game function, we only need to modify it.

Just use the TetrisMachine class, and then change the Player class accordingly. Everything is very convenient. However, it is convenient for the developers themselves, so what if one day the developers are no longer responsible for the project? This kind of logic is left to latecomers, and no one finds it convenient. There is an important principle in the actual model; you can close modifications and expansion development, which you can understand in detail.

In addition, another benefit of using the command mode is that it can realize the command recording function. For example, in the above example, we use a data structure in the requestor Buttons to store the executed command object, so that we can easily know what has just been executed. Which command actions have been executed and can be restored when needed. You can try the specific code yourself and will not be given here.

In the command mode, it fully embodies the common problems of almost all design modules, which is the expansion of classes and the creation of a large number of derived classes. This is an inevitable problem, but it also brings us many benefits. The coupling, flexible control and better scalability, however, whether it is necessary to use the command mode in the actual development process still needs to be considered.