Design Mode-command mode
Overview
In software development, we often need to send requests (call one or some methods) to some objects, but we do not know who the request receiver is, we do not know which operation is requested. At this time, we especially hope to design software in a loosely coupled way so that the coupling between request senders and request recipients can be eliminated, this makes the call relationship between objects more flexible and allows users to flexibly specify the request recipient and the requested operation. The command mode provides a perfect solution for this type of problem.
The command mode can completely decouple the request sender and receiver. There is no direct reference relationship between the sender and receiver. The object sending the request only needs to know how to send the request, instead of how to complete the request.
Definition
Command Pattern: encapsulate a request as an object, so that we can parameterize the customer with different requests; queue requests or record request logs, and supports unrecoverable operations. The command mode is an object behavior mode. Its alias is Action mode or Transaction mode.
The definition of the command mode is complex. Many terms are mentioned, such as "parameterization of customers with different requests" and "queuing of requests ", the terms "record request logs" and "Support for unrecoverable operations" will be explained in detail later.
The core of the command mode is to introduce the command class, which can reduce the coupling between the sender and the receiver. The request sender only needs to specify a command object, then, call the request recipient's processing method through the command object. Its structure is shown in 3:
The command mode structure includes the following roles:
● Command (Abstract Command class): An abstract Command class is generally an abstract class or interface, in which methods such as execute () used to execute requests are declared, these methods can call the related operations of the Request receiver.
● ConcreteCommand (specific command class): a specific command class is a subclass of the abstract command class. It implements the methods declared in the abstract command class, which correspond to specific recipient objects, bind the action of the recipient object. When the execute () method is implemented, the Action of the recipient object is called ).
● Invoker: the caller is the request sender. It executes the request through the command object. A caller does not need to determine its receiver during design, so it only has an association with the abstract command class. When running the program, you can inject a specific command object into it, and then call the execute () method of the specific command object to indirectly call related operations of the Request recipient.
● Receiver: the Receiver performs request-related operations and processes the request service.
The essence of the command mode is to encapsulate requests. A request corresponds to a command, which separates the responsibility for issuing commands from the responsibility for executing commands. Each Command is an operation: the Requesting Party sends a request to execute an operation. The receiving party receives the request and performs the corresponding operation. The command mode allows the requester and the receiver to be independent, so that the requester does not have to know the interfaces of the receiver, you do not need to know how requests are received, whether operations are executed, when the requests are executed, and how the requests are executed.
The key to the command mode is to introduce the abstract command class. The request sender program the abstract command class. Only specific commands that implement the abstract command class are associated with the request receiver. In the simplest abstract command class, only one abstract execute () method is included. Each specific command class stores an ER-type object as an instance variable, therefore, different command classes provide different implementations of the execute () method and call the request processing methods of different receivers.
Typical code of command mode
The typical abstract command code is as follows:
abstract class Command { public abstract void execute(); }
For the request sender (caller), the abstract command class is programmed. You can input a specific command class object at runtime by constructing an injection or setting a value injection, and call the execute () method of the command object in the business method. The typical code is as follows:
Class Invoker {private Command command Command; // construct the injection public Invoker (command Command) {this. command = command;} // set the value to public void setCommand (Command command) {this. command = command;} // business method, used to call the execute () method of the command class public void call () {command.exe cute ();}}
The specific command class inherits the abstract command class, which is associated with the request receiver and implements the execute () method declared in the abstract command class, the receiver's request response method action () is called during implementation. The typical code is as follows:
Class ConcreteCommand extends Command {private Receiver er extends er; // maintain a reference to the request receiver object public void execute () {receiver er. action (); // call the business processing method of the Request recipient action ()}}
The requester er Class provides the action () method to perform request-related operations. The typical code is as follows:
Class extends er {public void action () {// specific action }}
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. These request recipients will execute Business Methods one by one, process the request. In this case, we can use the command queue.
There are multiple methods to implement the command queue. One of the most common and flexible methods is to add a CommandQueue class, which is responsible for storing multiple command objects, different command objects can correspond to different request recipients. The typical code of the CommandQueue class is as follows:
Import java. util. *; class CommandQueue {// defines an ArrayList to store the command queue private ArrayList
commands = new ArrayList
(); Public void addCommand (Command command) {commands. add (command);} public void removeCommand (Command command) {commands. remove (command) ;}// call the execute () method public void execute () {for (Object command: commands) {(command1_commandcmd.exe cute () ;}} of each command Object cyclically ();}}}
After the CommandQueue command queue class is added, the request sender class Invoker will program the CommandQueue class. The code is modified as follows:
Class Invoker {private CommandQueue commandQueue; // maintain a reference to a CommandQueue object // construct a public Invoker (CommandQueue commandQueue) {this. commandQueue = commandQueue;} // set the value to public void setCommandQueue (CommandQueue commandQueue) {this. commandQueue = commandQueue;} // call the execute () method of the CommandQueue class public void call () {commandQueue.exe cute ();}}
The command queue is similar to the "batch processing" that we often call. Batch Processing, as the name implies, can be used to batch process a group of objects (commands). When a sender sends a request, a series of recipients will respond to the request, the command queue can be used to design a batch processing application. If the receiving order of the Request recipient is not strictly ordered, we can also use multithreading technology to concurrently call the execute () method of the command object, this improves the execution efficiency of the program.
Implementation of the Undo operation
In command mode, we can call the execute () method of a command object to process the request. If you need to cancel the (Undo) request, you can add a reverse operation to the command class.
In addition to Undo through a reverse operation, you can also save the historical state of the object to Undo it. The latter can be implemented using the Memento Pattern mode.
The following uses a simple example to learn how to use the command mode to perform the Undo operation:
The software company wants to develop a simple calculator, which can implement simple mathematical operations and cancel operations on the operations.
The software company Developers used the command mode to design the 5-chart structure. The CalculatorForm class on the calculator interface acts as the request sender, And the Adder class that implements the data summation function acts as the request receiver, the interface class can indirectly call the add () method in the addition class to implement addition operations, and provides the undo () method that can cancel addition operations.
The complete code of this instance is as follows:
// Addition class: the requested receiver class Adder {private int num = 0; // defines the initial value as 0 // addition operation. The incoming value and num are added each time, return the result to public int add (int value) {num + = value; return num ;}// abstract command class abstract class AbstractCommand {public abstract int execute (int value ); // declare the command execution method execute () public abstract int undo (); // declare the revocation method undo ()} // The specific command class ConcreteCommand extends AbstractCommand {private Adder adder = new Adder (); private int value; // implements the execute () method declared in the abstract command class, call the addition operation public int execute (int value) {this. value = value; return adder. add (value) ;}// implement the undo () method declared in the abstract command class. add an inverse number to implement the inverse public int undo () {return adder. add (-value) ;}// calculator Interface class: Request sender class CalculatorForm {private AbstractCommand command; public void setCommand (AbstractCommand command) {this. command = command;} // call the execute () method of the command object to execute the public void compute (int value) {int I = command.exe cute (value); System. out. println (the operation result is + I);} // call the undo () method of the command object to undo public void undo () {int I = command. undo (); System. out. println (Execution undo, Calculation Result: + I );}}
Write the following client test code:
Class Client {public static void main (String args []) {CalculatorForm form = new CalculatorForm (); AbstractCommand command; command = new ConcreteCommand (); form. setCommand (command); // inject the command object form to the sender. compute (10); form. compute (5); form. compute (10); form. undo ();}}
Compile and run the program. The output result is as follows:
Run the operation. The operation result is 10. The operation result is 15. The operation result is 25. The operation is canceled. The operation result is 15.
Note that only one Undo operation can be performed in this instance, because the Historical Status of the command object is not saved, you can import a command set or other methods to store the command status for each operation, so as to implement multiple Undo operations. In addition to the Undo operation, you can also use a similar method to perform a Redo operation, that is, to restore the Undo operation (or secondary revocation ).
Request log
The request Log stores the request history, which is stored permanently on the computer in the form of a Log File. Many systems provide log files, such as Windows Log Files and Oracle log files. log files can record user operations on the system (such as data changes ). The request log file provides many functions. Common functions are as follows:
(1) If the system fails, the log file can provide a recovery mechanism for the system. In the request log file, you can record each step of the system's operations, so that the system can be smoothly restored to a specific State;
(2) request logs can also be used for batch processing. In a request log file, you can store a series of command objects, such as a command queue;
(3) All command objects in the command queue can be stored in one log file. Each Command is executed to delete a corresponding command object from the log file, to prevent request loss due to power failure or system restart, and to avoid repeated execution of some commands when all requests are ressent, you only need to read the request log file, continue to execute the remaining commands in the file.
When implementing the request log, We Can serialize the command object to the log file. At this time, the command class must implement the java. io. Serializable interface. The following uses a simple example to describe the usage of the log file and how to implement the request log:
The software company has developed a website configuration file management tool, which allows you to add, delete, modify, and perform other operations on the website configuration file through a visual interface. The tool is designed using the command mode, as shown in structure 6:
Currently, software company Developers want to record the operation requests to the configuration file in the log file. If the website is redeployed, they only need to execute the command object stored in the log file to modify the configuration file.
The complete code of this instance is as follows:
Import java. io. *; import java. util. *; // abstract Command class. Because the Command object needs to be written to a file, it implements the Serializable interface abstract class Command implements Serializable {protected String name; // Command name protected String args; // Command parameter protected ConfigOperator configOperator; // maintain the public Command (String name) {this. name = name;} public String getName () {return this. name;} public void setName (String name) {this. name = name;} publi C void setConfigOperator (ConfigOperator configOperator) {this. configOperator = configOperator;} // declare two abstract execution methods: execute () public abstract void execute (String args); public abstract void execute ();} // Add command class: specific Command class InsertCommand extends Command {public InsertCommand (String name) {super (name);} public void execute (String args) {this. args = args; configOperator. insert (args);} public void exec Ute () {configOperator. insert (this. args) ;}}// modify the Command class: The specific Command class ModifyCommand extends Command {public ModifyCommand (String name) {super (name);} public void execute (String args) {this. args = args; configOperator. modify (args);} public void execute () {configOperator. modify (this. args) ;}/// the DELETE command class DeleteCommand // configuration file operation class: Request recipient. Because the ConfigOperator class object is a member object of the Command, it will also write files along with the Command object, so ConfigOperator also needs to implement the Serializable Interface class ConfigOperator implements Serializable {public void insert (String args) {System. out. println (add new node: + args);} public void modify (String args) {System. out. println (modify node: + args);} public void delete (String args) {System. out. println (delete node: + args) ;}// configuration file setting window class: Request sender class ConfigSettingWindow {// defines a set to store the command object private ArrayList for each operation
commands = new ArrayList
(); Private Command command; // inject a specific Command object public void setCommand (command Command) {this. command = command;} // execute the configuration file modification command, and add the command object to the command set public void call (String args) {command.exe cute (args); commands. add (command) ;}// record the request log, generate the log file, and write the command set to the log file public void save () {FileUtil. writeCommands (commands);} // extract the command set from the log file, and call the execute () method of each command object cyclically to reset the public void recover () in the configuration file () {ArrayList list; list = FileUtil. readCommands (); for (Object obj: list) {(command?obj=.exe cute () ;}}// tool class: file Operation class FileUtil {// write the command set to the log file public static void writeCommands (ArrayList commands) {try {FileOutputStream file = new FileOutputStream (config. log); // create an object output stream to write the object into the file. ObjectOutputStream objout = new ObjectOutputStream (new BufferedOutputStream (file); // write the object to the file objout. writeObject (commands); objout. clo Se ();} catch (Exception e) {System. out. println (saving failed !); E. printStackTrace () ;}}// command set extraction from log files public static ArrayList readCommands () {try {FileInputStream file = new FileInputStream (config. log); // create an object input stream to read the object ObjectInputStream objin = new ObjectInputStream (new BufferedInputStream (file) from the file )); // read the object in the file and convert it to ArrayList-type ArrayList commands = (ArrayList) objin. readObject (); objin. close (); return commands;} catch (Exception e) {System. out. println (command read Failed !); E. printStackTrace (); return null ;}}}
Write the following client test code:
Class Client {public static void main (String args []) {ConfigSettingWindow csw = new ConfigSettingWindow (); // defines the request sender's Command command; // define the command object ConfigOperator co = new ConfigOperator (); // define the request receiver // change the configuration file four times command = new InsertCommand (added); command. setConfigOperator (co); csw. setCommand (command); csw. call (website homepage); command = new InsertCommand (added); command. setConfigOperator (co); csw. setCommand (command); csw. call (port number); command = new ModifyCommand (modify); command. setConfigOperator (co); csw. setCommand (command); csw. call (website homepage); command = new ModifyCommand (modify); command. setConfigOperator (co); csw. setCommand (command); csw. call (port number); System. out. println (----------------------------); System. out. println (save configuration); csw. save (); System. out. println (----------------------------); System. out. println (Restore configuration); System. out. println (----------------------------); csw. recover ();}}
Compile and run the program. The output result is as follows:
Add new node: Add new node on the homepage: Modify port number node: Modify site homepage node: Save configuration -------------------------------------------- restore configuration ---------------------------- add new node: Add new node on the homepage: port Number modification node: website homepage modification node: Port Number
Macro command
A Macro Command is also called a combined Command. It is a product of the combination mode and Command mode. A macro command is a specific command class that has a set attribute that contains references to other command objects. Generally, macro commands do not directly interact with the request receiver, but call the receiver's method through its members. When the execute () method of a macro command is called, The execute () method of each member command in the macro command is recursively called. A member of a macro command can be a simple command, you can continue to use macro commands. Executing a macro command triggers the execution of multiple specific commands to batch process the commands. Its structure is 7:
Command mode Summary
The command mode is a design mode with a very high frequency. It can decouple the request sender from the receiver. The request sender indirectly references the request recipient through the command object, this makes the system more flexible and scalable. In GUI-based software development, the command mode has been widely used in computer desktop applications and mobile applications.
Advantages of command mode
The command mode has the following advantages:
(1) Reduce the Coupling Degree of the system. Because there is no direct reference between the requester and the receiver, the requester and the receiver are completely decoupled. The same requester can correspond to different receivers. Similarly, the same receiver can also be used by different requestors, with good independence between the two.
(2) New commands can be easily added to the system. Because the addition of a new command class does not affect other classes, it is easy to add a new command class without modifying the original system source code or even the customer class code, meets the requirements of the "Open and closed principle.
(3) You can easily design a command queue or macro command (combined command ).
(4) it provides a design and implementation solution for Undo and Redo operations.
Disadvantages of command mode
The main disadvantages of the command mode are as follows:
Using the command mode may cause some systems to have too many specific command classes. Because a specific command class is designed for each call operation to the request receiver, a large number of specific command classes may be required in some systems, which will affect the use of the command mode.
Applicable scenarios of command mode
You can use the command mode in the following cases:
(1) The system needs to decouple the request caller from the request receiver so that the caller and the receiver do not directly interact. The caller of the request does not need to know the existence of the receiver or who the receiver is. The receiver does not need to care about the call time.
(2) The system must specify the request, queue the request, and execute the request at different times. A command object and the initial caller of the request can have different lifecycles. In other words, the sender of the initial request may no longer exist, and the command object itself is still active, you can use this command object to call the request receiver, without having to worry about the existence of the Request caller. This can be achieved through a mechanism such as request log files.
(3) The system must support Undo and Redo operations of commands.
(4) The system must combine a group of operations to form macro commands.