This is the fourth day of my participation in the August More text Challenge. For details, see:August is more challenging

Preface: Command patterns are often used, and if we do not understand the structure and definition of command patterns, we will not be able to use them.

For example: during winForm development, we often had to use the same interface for file downloads, but not everywhere used the same download logic for files, and then the download interface can be the same interface.

In order to reuse the download interface (download display, progress bar, etc.) in the future, we often define the download execution operation as an interface, implement the interface in specific use, and set the specific execution object to the download interface. When the download button is pressed, the specific execution object (receiver) of the setting is called to perform the processing of the download.

Let’s take a look at the details and implementation of the command mode, and think back to when we accidentally use the command mode, so that we can use professional terms in future communication.

1. Application scenarios of remote control

HeadFirst design Patterns uses the remote control as an example to implement the command mode, and explains the object and structure of the command mode in order to order food in a restaurant. For the sake of clear logic, we will not mix the two methods, and we will only use the remote control as an example.

Now the requirement is to have a remote control, on which there are switches to control various electrical appliances, and the executive control appliances of the switch are the devices (objects) developed by various manufacturers and inserted into the slots of the corresponding switch positions. Based on these conditions, we implement the remote control system.

A simple solution is to make a switch identifier and make an IF judgment based on the switch type when a switch is pressed. If slot1==Light,then light.on(), else if slot1==Tv then TV.on () this code will appear in a heap, which is bad for adding or decreasing switches or replacing switches later. For the design of the remote control class, we should keep the remote control code as simple as possible, and not worry about how the specific manufacturer class implements it. So we should encapsulate execution in a command object, so we try to implement the remote step by step.

First we define a uniform connection for the command object.

The interface has only one simple execute command method.

Public void execute(); public void execute(); }Copy the code

Next we implement a command to turn on the light

Public class Light {public void on() {console. WriteLine(" turn on the Light "); } public void off() {console. WriteLine(" turn off the light "); } } public class LightOnCommand : Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); }}Copy the code

Let’s just assume for simplicity that the remote control has only one switch, implement the remote control.

Public class SimpleRemoteControl {// card slot Command slot; public void setCommand(Command command) { slot = command; Public void ButtonWasPressed() {slot.execute(); }}Copy the code

test

static void Main(string[] args) { SimpleRemoteControl remoteControl = new SimpleRemoteControl(); Light Light = new Light(); LightOnCommand = new LightOnCommand(light); // set the command object corresponding to the remoteControl switch remoteControl. SetCommand (lightOnCommand); remoteControl.ButtonWasPressed(); Console.ReadKey(); }Copy the code

  

2. Command mode and class diagram

In the example above, we have used command mode to implement a simple remote control, and recall that the download file button operation is a typical application scenario where command mode can be used.

The only thing is that we may not have an execution class designed by another vendor. We may implement the execute logic directly in the command object that inherits the interface, instead of calling another receiver to execute it.

This is the “smart” command object, and above we implemented the “fool” command object. We’ll come back to this later, but first we’ll look at the command pattern definition and drawing the class diagram.

Command mode: Encapsulate “requests” into objects so that different requests, queues, or logs can be used to parameterize other objects. Command mode also supports undo operations.

3. Complete multi-switch remote control and undo operation

Suppose the remote control now has five switches. We already have experience with simple remote controls, so we can set the command objects for the other four switches. Define two arrays to record the command object corresponding to the switch.

public class RemoteControl { Command[] onCommands; Command[] offCommands; public RemoteControl() { onCommands = new Command[5]; offCommands = new Command[5]; Command noCommand = new NoCommand(); for (int i = 0; i < 5; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } public void setCommand(int slot,Command commandOn, Command commandOff) { onCommands[slot] = commandOn; offCommands[slot] = commandOff; Public void OnButtonWasPressed(int slot) {onCommands[slot].execute(); } public void OffButtonWasPressed(int slot) {offCommands[slot].execute(); Public override string ToString() {var sb = new StringBuilder("\n------------Remote Control-----------\n"); for (int i = 0; i < onCommands.Length; i++) { sb.Append($"[slot{i}] {onCommands[i].GetType()}\t{offCommands[i].GetType()} \n"); } return sb.ToString(); }}Copy the code

In the remote control, we define a Nocommand class to initialize the command object for the corresponding switch on the remote control, to avoid reporting null errors or to eliminate the judgment that the command object is null when the switch calls it.

public void OnButtonWasPressed(int slot) { if(onCommand[slot]! =null)) onCommands[slot].execute(); }Copy the code

We see this use of initial values or empty objects in many design patterns. Even empty objects themselves are sometimes considered a design pattern. (This code feels more elegant O)

The remote is done, and we have one more job to do, which is undo.

Undo we also define an undo method in the command interface.

Public void execute(); public void execute(); Public void undo(); }Copy the code

Then we have LightOnCommand implement the undo method and add the LightOffCommand command object.

public class LightOnCommand : Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); } public void undo() { light.off(); } } class LightOffCommand : Command { Light light; public LightOffCommand(Light light) { this.light = light; } public void execute() { light.off(); } public void undo() { light.on(); }}Copy the code

Add the undo button operation UndoButtonWasPressed to the remote and store the last operation with the undoCommand property.

public class RemoteControl { Command[] onCommands; Command[] offCommands; Command undoCommand; public RemoteControl() { onCommands = new Command[5]; offCommands = new Command[5]; Command noCommand = new NoCommand(); for (int i = 0; i < 5; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } public void setCommand(int slot,Command commandOn, Command commandOff) { onCommands[slot] = commandOn; offCommands[slot] = commandOff; Public void OnButtonWasPressed(int slot) {onCommands[slot].execute(); undoCommand = onCommands[slot]; } public void OffButtonWasPressed(int slot) {offCommands[slot].execute(); undoCommand = offCommands[slot]; } public void UndoButtonWasPressed() { undoCommand.undo(); Public override string ToString() {var sb = new StringBuilder("\n------------Remote Control-----------\n"); for (int i = 0; i < onCommands.Length; i++) { sb.Append($"[slot{i}] {onCommands[i].GetType()}\t{offCommands[i].GetType()} \n"); } return sb.ToString(); }}Copy the code

Testing:

4. Supplement and summarize

Supplement:

(1) the command receiver is not necessarily exist, previously mentioned “smart” and “fool” command object, if the “smart” command object design, degree of decoupling between the caller and receiver than “fool” command object, but we can still be used in the use of relatively simple “smart” command object design.

② Undo example we only did the return last operation, if we want to undo many times we can save the operation record to the stack, no matter when undo, we can take out the top command object from the stack to perform the undo operation.

Command mode is often used to queue requests, log requests. The execution method is called when the queue picks up the stored command objects in order, regardless of what is executed.

Log requests can be used in some cases to log all actions and to restore the previous state (undo) after a system crash. For more advanced applications, these techniques can be applied to transaction processing.

Through simple to further implementation of the command pattern and some flexibility and need to pay attention to the point, any understanding is not in place welcome correction.