[Twelve] Design Patterns ~~~ Behavioral Patterns ~~~ Command Patterns (Java)

Command Pattern-Command Pattern【Learning Difficulty: ★★★☆☆, Frequency of Use: ★★★★☆】

1.1. Pattern Motivation

        In software design, we often need to send requests to some objects, but we don't know who the recipient of the request is, nor which operation is being requested. We only need to specify the specific request recipient when the program is running. Yes, at this time, you can use the command mode to design, so that the request sender and the request receiver can eliminate the coupling between each other, and make the calling relationship between objects more flexible.
        The command mode can completely decouple the sender and receiver. There is no direct reference relationship between the sender and the receiver. The object that sends the request only needs to know how to send the request, but does not need to know how to complete the request. This is the pattern motivation of the command pattern.

Case 1

        One of the last steps in decorating a new home is installing sockets and switches, which can be used to turn on and off some electrical appliances, such as lights or exhaust fans. When purchasing a switch, we don't know what electrical appliance it will be used to control. That is to say, the switch is not directly related to lights and exhaust fans. A switch may be used to control lights after installation, or it may be used to control Exhaust fans or other electrical equipment. The connection between the switch and the electrical appliances is established through wires. If the switch is turned on, the wires are energized and the appliances work; otherwise, the switch is turned off, the wires are powered off, and the appliances stop working. The same switch can control different appliances through different wires, as shown in Figure 1:
insert image description here

        In Figure 1, we can understand the switch as a request sender through which the user sends a "turn on the light" request, and the light is the final receiver and processor of the "turn on the light" request. In the figure, the switch There is no direct coupling relationship with the lamp, they are connected together by wires, different request receivers can be connected by using different wires, just change one wire, the same sender (switch) can correspond to different receivers Those (electrical appliances).
        In software development, there are also many request sender and receiver objects similar to switches and electrical appliances. For example, a button may be the sender of a "close window" request, and the button click event processing class is the receiver of the request. By. In order to reduce the coupling of the system and decouple the sender and receiver of the request, we can use a design pattern called command mode to design the system. In the command mode, the sender and receiver are introduced The new command object (similar to the wire in Figure 1) encapsulates the sender's request in the command object, and then calls the receiver's method through the command object. In this chapter we will learn the command pattern for decoupling request senders and receivers.

Case 2 (custom function keys)

        The developer of Sunny Software Company developed a desktop application for the company's internal OA system. This application provides users with a series of custom function keys, and users can use these function keys to achieve some shortcut operations. Through analysis, the developers of Sunny Software Company found that different users may have different usage habits. Everyone has their own preferences when setting function keys. For example, some people like to set the first function key as "Open Help" document”, some people like to set the function key as “minimize to tray”. In order to allow users to set the function keys flexibly, the developer provides a “function key setting” window, the interface of which is shown in the figure 2 shows:
insert image description here

        Through the interface shown in Figure 2, users can bind function keys and corresponding functions together, and modify the settings of function keys as needed, and the system may add some new functions or function keys in the future.

        A developer of Sunny Software Company intends to use the following code to realize the call relationship between function keys and function processing classes:

//FunctionButton:功能键类,请求发送者
class FunctionButton {
    
    
	private HelpHandler help; //HelpHandler:帮助文档处理类,请求接收者
	
    //在FunctionButton的onClick()方法中调用HelpHandler的display()方法
public void onClick() {
    
    
		help = new HelpHandler();
		help.display(); //显示帮助文档
	}
}

        In the above code, the function key class FunctionButton acts as the sender of the request, and the help document processing class HelpHandler acts as the receiver of the request. In the onClick() method of the sender FunctionButton, the display() method of the receiver HelpHandler will be called. Obviously, if the above code is used, it will bring the following problems to the system:

  • (1) Since there is a direct method call between the request sender and the request receiver, the coupling degree is very high. To replace the request receiver, the source code of the sender must be modified. If it is necessary to change the request receiver HelpHandler to WindowHanlder (window handling class ), you need to modify the source code of FunctionButton, which violates the "opening and closing principle".
  • (2) The function of the FunctionButton class has been fixed during design and implementation. If a new request receiver is added, if the original FunctionButton class is not modified, a new class with a function similar to the FunctionButton must be added, which will cause the system to The number of middle classes increases dramatically. Since there may not be any relationship between the request receivers HelpHandler, WindowHanlder and other classes, they do not have a common abstraction layer, so it is difficult to design FunctionButton according to the "dependency inversion principle".
  • (3) Users cannot set the function of a function key according to their needs. Once the function of a function key is fixed, its function cannot be changed without modifying the source code, and the system lacks flexibility.

        It is not difficult to know that all these problems are caused by the direct coupling relationship between the request sender FunctionButton class and the request receiver HelpHandler, WindowHanlder and other classes. How to reduce the coupling degree between the request sender and receiver so that the same Can the sender correspond to different receivers? This is a problem that the developers of Sunny Software Company have to consider when designing the "Function Key Setting" module. The command mode was born to solve such problems. At this time, if we use the command mode, the above problems can be solved to a certain extent (Note: the command mode cannot solve the problem of increasing the number of classes)

1.2. Schema Definition

        Command Pattern: Encapsulate a request as an object, so that we can parameterize customers with different requests; queue requests or record request logs, and support undoable operations. Command mode is an object behavior mode, its alias is action (Action) mode or transaction (Transaction) mode.

1.3. Schema structure

Command mode includes the following roles:

  • Command: An abstract command class . An abstract command class is generally an abstract class or interface, in which methods such as execute() for executing requests are declared, and related operations of the request receiver can be invoked through these methods.
  • ConcreteCommand: Concrete command class , the concrete command class is a subclass of the abstract command class, which implements the method declared in the abstract command class, which corresponds to the specific receiver object, and binds the action of the receiver object to it. When implementing the execute() method, the relevant operation (Action) of the receiver object will be called.
  • Invoker: Invoker , the caller is the sender of the request, which executes the request through the command object. A caller does not need to determine its receiver at design time, so it only has an association relationship with the abstract command class. When the program is running, a specific command object can be injected into it, and then the execute() method of the specific command object can be called, so as to realize the related operations of indirectly invoking the request receiver.
  • Receiver: Receiver , the receiver performs operations related to the request, and it specifically implements the business processing of the request.
    insert image description here

        The essence of the command mode is to encapsulate the request, a request corresponds to a command, and the responsibility for issuing the command is separated from the responsibility for executing the command . Each command is an operation: the requesting party sends a request to perform an operation; the receiving party receives the request and performs the corresponding operation. The command mode allows the requesting party and the receiving party to be independent, so that the requesting party does not have to know the interface of the receiving party, let alone how the request is received, whether the operation is executed, when it is executed, and how it is executed. of .
         The key of the command mode is the introduction of an abstract command class. The request sender programs for the abstract command class, and only the specific commands that implement the abstract command class are associated with the request receiver . The simplest abstract command class contains only an abstract execute() method. Each specific command class stores an object of Receiver type as an instance variable, thereby specifying a request receiver. Different specific commands The class provides different implementations of the execute() method and invokes the request processing methods of different receivers.

A typical abstract command class code is as follows:

abstract class Command {
    
    
	public abstract void execute();
}

        For the sender of the request, that is, the caller, programming will be performed for the abstract command class, and the specific command class object can be passed in at runtime through construction injection or set value injection, and the execute() of the command object can be called in the business method method, its typical code is as follows:

class Invoker {
    
    
	private Command command;
	
    //构造注入
	public Invoker(Command command) {
    
    
		this.command = command;
	}
	
    //设值注入
	public void setCommand(Command command) {
    
    
		this.command = command;
	}
	
	//业务方法,用于调用命令类的execute()方法
	public void call() {
    
    
		command.execute();
	}
}

        The specific command class inherits the abstract command class, which is associated with the request receiver, implements the execute() method declared in the abstract command class, and calls the receiver's request response method action() when it is implemented. The typical code is as follows Shown:

class ConcreteCommand extends Command {
    
    
	private Receiver receiver; //维持一个对请求接收者对象的引用
 
	public void execute() {
    
    
		receiver.action(); //调用请求接收者的业务处理方法action()
	}
}

        The Receiver class of the request receiver specifically implements the business processing of the request. It provides the action() method to perform operations related to the request. The typical code is as follows:

class Receiver {
    
    
	public void action() {
    
    
		//具体操作
	}
}

1.4. Timing diagram

insert image description here

1.5. Code Analysis

        In order to reduce the coupling between function keys and function processing classes, and allow users to customize the function of each function key, Sunny software company developers use the command mode to design the "custom function key" module, and its core structure is shown in Figure 4 Shown:

1.5.1 Production

        In Figure 4, FBSettingWindow is the "function key setting" interface class, FunctionButton acts as a request caller, Command acts as an abstract command class, MinimizeCommand and HelpCommand act as specific command classes, and WindowHanlder and HelpHandler act as request receivers. The full code is as follows:

package com.zyz.demo;

import java.util.ArrayList;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/25 18:24
 * @Description:
 */

//功能键设置窗口类

class FBSettingWindow {
    
    
    private String title; //窗口标题
    /**
     * 定义一个ArrayList来存储所有功能键
     */
    private ArrayList<FunctionButton> functionButtons = new ArrayList<FunctionButton>();

    public FBSettingWindow(String title) {
    
    
        this.title = title;
    }

    public void setTitle(String title) {
    
    
        this.title = title;
    }

    public String getTitle() {
    
    
        return this.title;
    }

    public void addFunctionButton(FunctionButton fb) {
    
    
        functionButtons.add(fb);
    }

    public void removeFunctionButton(FunctionButton fb) {
    
    
        functionButtons.remove(fb);
    }


    /**
     * 显示窗口及功能键
     */
    public void display() {
    
    
        System.out.println("显示窗口:" + this.title);
        System.out.println("显示功能键:");
        for (Object obj : functionButtons) {
    
    
            System.out.println(((FunctionButton) obj).getName());
        }
        System.out.println("------------------------------");
    }

}


//功能键类:请求发送者

class FunctionButton {
    
    
    private String name; //功能键名称
    private Command command; //维持一个抽象命令对象的引用

    public FunctionButton(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return this.name;
    }

    /**
     * 为功能键注入命令
     *
     * @param command
     */
    public void setCommand(Command command) {
    
    
        this.command = command;
    }

    /**
     * 发送请求的方法
     */
    public void onClick() {
    
    
        System.out.print("点击功能键盘:");
        command.execute();
    }


}

//抽象命令类

abstract class Command {
    
    
    public abstract void execute();
}


//帮助命令类:具体命令类

class HelpCommand extends Command {
    
    
    private HelpHandler hhObj; //维持对请求接收者的引用

    public HelpCommand() {
    
    
        hhObj = new HelpHandler();
    }

    /**
     * 命令执行方法,将调用请求接收者的业务方法
     */
    @Override
    public void execute() {
    
    
        hhObj.display();
    }
}


//最小化命令类:具体命令类

class MinimizeCommand extends Command {
    
    
    private WindowHanlder whObj; //维持对请求接收者的引用

    public MinimizeCommand() {
    
    
        whObj = new WindowHanlder();
    }

    /**
     * 命令执行方法,将调用请求接收者的业务方法
     */
    @Override
    public void execute() {
    
    
        whObj.minimize();
    }
}


//窗口处理类:请求接收者

class WindowHanlder {
    
    
    public void minimize() {
    
    
        System.out.println("将窗口最小化至托盘!");
    }
}

//帮助文档处理类:请求接收者

class HelpHandler {
    
    
    public void display() {
    
    
        System.out.println("显示帮助文档!");
    }
}

1.5.2 Client

Write the following client test code:

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/25 19:09
 * @Description:
 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
        FBSettingWindow fbsw = new FBSettingWindow("功能键设置");

        FunctionButton fb1,fb2;
        fb1 = new FunctionButton("功能键1");
        fb2 = new FunctionButton("功能键1");

        //生成具体命令对象
        Command command1,command2;
        command1 = new MinimizeCommand();
        command2 = new HelpCommand();

        //将命令对象注入功能键
        fb1.setCommand(command1);
        fb2.setCommand(command2);

        fbsw.addFunctionButton(fb1);
        fbsw.addFunctionButton(fb2);
        fbsw.display();

        //调用功能键的业务方法
        fb1.onClick();
        fb2.onClick();
    }
}

1.5.3 Test results

insert image description here

1.6. Pattern Analysis

        The essence of the command mode is to encapsulate the command and separate the responsibility of issuing the command from the responsibility of executing the command.

  • Each command is an operation: the requesting party sends out a request to perform an operation; the receiving party receives the request and performs the operation.
  • The command mode allows the requesting party and the receiving party to be independent, so that the requesting party does not have to know the interface of the receiving party, let alone how the request is received, whether the operation is executed, when it is executed, and how being executed.
  • The command pattern makes the request itself an object that can be stored and passed around like any other object.
  • The key of the command mode is the introduction of an abstract command interface, and the sender programs for the abstract command interface, and only the specific commands that implement the abstract command interface can be associated with the receiver.

1.7. Examples

Example 1: TV remote control

  • The TV is the receiver of the request, and the remote control is the sender of the request. There are some buttons on the remote control, and different buttons correspond to different operations of the TV. The role of the abstract command is played by a command interface. There are three specific command classes that implement the abstract command interface. These three specific command classes represent three operations: turn on the TV, turn off the TV, and switch channels. Obviously, the TV remote control is a typical application example of command mode.
    insert image description here

Timing diagram
insert image description here

1.8. Advantages

Advantages of Command Pattern

  • Reduce system coupling.
  • New commands can be easily added to the system.
  • It is relatively easy to design a command queue and macro commands (combined commands).
  • Undo and Redo of requests can be easily realized.

1.9. Disadvantages

Disadvantages of Command Pattern

  • Using the command pattern may result in some systems having too many concrete command classes. Because a specific command class needs to be designed for each command, some systems may require a large number of specific command classes, which will affect the use of the command mode.

1.10. Applicable environment

Command mode can be used when:

  • The system needs to decouple the request caller and request receiver so that the caller and receiver do not interact directly.
  • The system needs to specify requests, queue them, and execute them at different times.
  • The system needs to support the command's undo (Undo) operation and recovery (Redo) operation.
  • The system needs to group a set of operations together, i.e. support macro commands

1.11. Pattern application

        Many systems provide macro command functions, such as Shell programming under the UNIX platform, which can encapsulate multiple commands in a command object, and only need a simple command to execute a command sequence, which is also one of the application examples of the command mode. one.

1.12. Schema extensions

Macro command is also called combination command, which is the product of the combination of command mode and combination mode.

  • A macro command is also a specific command, but it contains references to other command objects. When calling the execute() method of a macro command, it will recursively call the execute() method of each member command it contains. A macro command Member objects can be simple commands and can continue to be macro commands. Executing a macro command will execute multiple specific commands, thereby realizing batch processing of commands.

1.13. Summary

  • In the command mode, a request is encapsulated as an object, so that we can parameterize clients with different requests; queue requests or record request logs, and support undoable operations. Command mode is an object-behavioral mode, and its alias is action mode or transaction mode.
  • The command mode includes four roles: the abstract command class declares methods such as execute() for executing the request, through which the relevant operations of the request receiver can be called; the specific command class is a subclass of the abstract command class, which implements the The method declared in the abstract command class corresponds to the specific receiver object and binds the actions of the receiver object to it; the caller is the sender of the request, also known as the requester, and it executes the request through the command object; the receiver Execute the operations related to the request, which specifically realizes the business processing of the request.
  • The essence of the command mode is to encapsulate the command and separate the responsibility of issuing the command from the responsibility of executing the command. The command pattern makes the request itself an object that can be stored and passed around like any other object.
  • The main advantage of the command mode is that it reduces the coupling degree of the system, it is very convenient to add new commands, and it is relatively easy to design a command queue and macro commands, and it is convenient to realize the cancellation and restoration of the request; its main disadvantage is that it may cause some Some systems have too many concrete command classes.
  • The application of the command mode includes: it is necessary to decouple the request caller and the request receiver, so that the caller and the receiver do not interact directly; it is necessary to specify requests, queue requests, and execute requests at different times; it is necessary to support command cancellation operations and To restore operations, a group of operations needs to be combined together, that is, macro commands are supported.

1.14. Implementation of command queue

        Sometimes we need to queue multiple requests. When a request sender sends a request, more than one request receiver will generate a response, and these request receivers will execute business methods one by one to complete the processing of the request. At this point, we can achieve it through the command queue.

        There are many ways to implement command queues. The most common and flexible way is to add a CommandQueue class, which is responsible for storing multiple command objects, and different command objects can correspond to different request reception. Alternatively, the typical code for the CommandQueue class is as follows:

import java.util.*;
 
class CommandQueue {
    
    
    //定义一个ArrayList来存储命令队列
	private ArrayList<Command> commands = new ArrayList<Command>();
	
	public void addCommand(Command command) {
    
    
		commands.add(command);
	}
	
	public void removeCommand(Command command) {
    
    
		commands.remove(command);
	}
	
    //循环调用每一个命令对象的execute()方法
	public void execute() {
    
    
		for (Object command : commands) {
    
    
			((Command)command).execute();
		}
	}
}

        After adding the command queue class CommandQueue, the request sender class Invoker will program against CommandQueue, and the code is modified as follows:

class Invoker {
    
    
	private CommandQueue commandQueue; //维持一个CommandQueue对象的引用
	
    //构造注入
	public Invoker(CommandQueue commandQueue) {
    
    
		this. commandQueue = commandQueue;
	}
	
    //设值注入
	public void setCommandQueue(CommandQueue commandQueue) {
    
    
		this.commandQueue = commandQueue;
	}
	
	//调用CommandQueue类的execute()方法
	public void call() {
    
    
		commandQueue.execute();
	}
}

        The command queue is somewhat similar to what we often call "batch processing". Batch processing, as the name implies, can batch process a group of objects (commands). When a sender sends a request, a series of receivers will respond to the request. Command queues can be used to design batch processing applications. If the request receiver There is no strict order in which the commands are received. We can also use multi-threading technology to call the execute() method of the command object concurrently, thereby improving the execution efficiency of the program.

1.15. Undo operation

        In the command mode, we can process the request by calling the execute() method of a command object. If we need to cancel (Undo) the request, we can implement it by adding a reverse operation in the command class.

Extension: In addition to realizing undo through a reverse operation, undo can also be realized by saving the historical state of the object, which can be realized by using the memento pattern (MementoPattern).

        Sunny Software Company intends to develop a simple calculator, which can realize simple mathematical operations, and can also perform undo operations on operations.

        The developers of Sunny Software Company used the command mode to design the structure diagram shown in Figure 5, in which the calculator interface class CalculatorForm acts as the request sender, and the addition class Adder that realizes the data summation function acts as the request receiver, and the interface class can indirectly call addition The add() method in the class implements the addition operation, and provides the undo() method that can undo the addition operation.
insert image description here

The complete code of this example is as follows:

//加法类:请求接收者
class Adder {
    
    
	private int num=0; //定义初始值为0
	
    //加法操作,每次将传入的值与num作加法运算,再将结果返回
	public int add(int value) {
    
    
		num += value;
		return num;
	}
}
 
//抽象命令类
abstract class AbstractCommand {
    
    
	public abstract int execute(int value); //声明命令执行方法execute()
	public abstract int undo(); //声明撤销方法undo()
}
 
//具体命令类
class ConcreteCommand extends AbstractCommand {
    
    
	private Adder adder = new Adder();
	private int value;
		
	//实现抽象命令类中声明的execute()方法,调用加法类的加法操作
public int execute(int value) {
    
    
		this.value=value;
		return adder.add(value);
	}
	
    //实现抽象命令类中声明的undo()方法,通过加一个相反数来实现加法的逆向操作
	public int undo() {
    
    
		return adder.add(-value);
	}
}
 
//计算器界面类:请求发送者
class CalculatorForm {
    
    
	private AbstractCommand command;
	
	public void setCommand(AbstractCommand command) {
    
    
		this.command = command;
	}
	
    //调用命令对象的execute()方法执行运算
	public void compute(int value) {
    
    
		int i = command.execute(value);
		System.out.println("执行运算,运算结果为:" + i);
	}
	
    //调用命令对象的undo()方法执行撤销
	public void undo() {
    
    
		int i = command.undo();
		System.out.println("执行撤销,运算结果为:" + i);
	}
}

编写如下客户端测试代码:
class Client {
    
    
	public static void main(String args[]) {
    
    
		CalculatorForm form = new CalculatorForm();
		AbstractCommand command;
		command = new ConcreteCommand();
		form.setCommand(command); //向发送者注入命令对象
		
		form.compute(10);
		form.compute(5);
		form.compute(10);
		form.undo();
	}
}

Compile and run the program, the output is as follows:
insert image description here

Guess you like

Origin blog.csdn.net/weixin_43304253/article/details/130985594
Recommended