This series of articles is a record of my thought process in learning design patterns, without any guarantee of accuracy or rigor.

1. Definition of schema

As a behavioral design pattern, the standard definition of command pattern is:

Encapsulate requests as objects that allow you to parameterize different requests to implement queuing or logging. Command mode can also support undo operations.

Its UML class diagram:

For the sake of discussion, we can use this scenario from the Head First Design Patterns book:

scenario

It is necessary to design a remote control for a home appliance automation company to control a bunch of appliances from different manufacturers. This remote control has seven programmable slots, each slot corresponds to a set of switch buttons, and has an integral undo button.

The implementation code

  • Abstract the command

    public interface Command
    {
        // The method of executing the command
        public void execute(a);
        // Undo the command method
        public void undo(a);
    }
    Copy the code
  • Specific electrical order

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

    public class RemoteControl
    {
        Command[] onCommands;
        Command[] offCommands;
        Command undoCommand;
        public RemoteControl(a)
        {
            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;
        }
     
        // Press the switch
        public void OnButtonWasPressed(int slot)
        {
            onCommands[slot].execute();
            undoCommand = onCommands[slot];
        }
        // Turn off the switch
        public void OffButtonWasPressed(int slot)
        {
            offCommands[slot].execute();
            undoCommand = offCommands[slot];
        }
     
        public void UndoButtonWasPressed(a) {
            undoCommand.undo();
        }
        // Prints out the array command object
        public override string ToString(a) {
            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");
            }
            returnsb.ToString(); }}Copy the code

2. Process thinking

From scene to pattern introduction

From the scenario listed above, we need to provide a remote control class that manages the on-off functionality of products from existing vendors and other vendors that may be added in the future. To accomplish this requirement, we can break it down as follows:

  1. Functions that need to be done

    The functions that need to be accomplished are also very clear, of course we also need to look at the two roles on the table: the remote control class and the multi-vendor appliance class (which provides various switching methods). We need for the remote control to provide a simple and easy to extend the integration mode, compatible with existing and future possible changes of electrical appliances, and integrated manner mentioned how simple, it is better to don’t need to pipe the actual remote control electric switch operation how to happen, it only need to tell the best electrical objects: open, close, simple can control them.

  2. How to Deal with change

    In the last step, we talked about providing a simple and easily extensible way to integrate appliances with remote controls. In this step, we thought about how to do this. Simple say first, according to our idea, if only need to tell the electrical object is open or closed, electrical appliances will be able to complete their own operation, appliances, then passed to the controller should have the same abstract, so as to make operation simple, but in fact these electric switch operating method of a class, each have each way is not unified, we might think of here, Provide an on and off protocol that lets the appliance classes implement themselves, thus completing a simple abstraction. But as we know, the vendor class directly modify is not a good way, because of considering the vendor class portability and future update, we should not directly modify these classes and meet our abstract interface, and the manufacturer does not necessarily provide the source code, and provides only a binary library and external operation interface.

    In fact, at this point, we can think of another pattern, the adapter pattern, through which we can satisfy the new interface without modifying the class itself. We just need to create an adapter class to implement our abstraction by delegating to the recipient. So we started creating different adapter classes for each of our appliance vendors. Of course, our adapter actually does more work than a typical adapter, because it might not just be simple data conversion, but it might be a little bit closer to the transparency that a facade mode does.

    When we talk about scalability, what we really want to talk about is whether the impact of future changes on us can be controlled by our elastic design. We should evaluate and envision future changes, so is there a design that can accommodate any change? I feel that no one can guarantee such perfection, and it is bound to make me feel uncomfortable to design for such an imaginary purpose, and even to have such an idea at work will cause the delay of the project. The idea of change in the future, need to keep in a certain field and also to have certain experience in this direction for the future to have relatively conforms to the evolution of thinking forecast, too much unrealistic idea I think is a waste of time and experience, this is impractical, the first is in this direction is not in conformity with the said earlier have certain experience to conform to the thinking of evolution, The second is not in line with the actual situation of the current company. When we forecast the reasonable change trend, has to do is to change the same part and isolation, and make the change in the process, we need to do enough simple and less, if you want to be a more general principle, that is the open closed principle, to cope with change, we come in do not change the previous code, but adding new code.

    So when we look at the previous simple response design, does it actually meet the requirements of the extension? I think it does, because it abstracts the operation. Don’t forget we also need to support undo, can undo be done in the current design? Since our abstract interface provides both open and close methods, if we add an undo method, we need to record whether the previous step was “open” or “close”, and finally decide in the undo method. We even have to write these records and judgments in every “adapter” class, which is really not good object-oriented design, so what should we do? At present, I have two ideas. First, we modify the abstract interface into an abstract class and encapsulate all the repeated parts in the abstract class. Alternatively, we wrap the on and off operations separately, so that there is only one operation per adapter class, and so there is only one previous undo step.

    While considering which way to use the above question, we also consider whether the remote control will add other Settings in the future, such as the wind speed of the electric fan, but not necessarily all appliances have adjustment functions, such as a router. Combining this problem, we return to the above and propose two solutions to the problem. The first solution does nothing to help the current problem and actually brings up another problem: Our interface does not conform to the principle of single responsibility, because it does not conform to this principle, our interface is not suitable for all manufacturers of electrical appliances, so we consider the second solution, on and off packaging separately, how to package? Do we design two interfaces? An on and an off? It must not, and actually we even do not accord with the basic simplicity, this will affect remote control type of extensibility, because the function of the remote future you must always support and off, in the future, and more than one function, have to add a foreign methods, and remove a set function, have to delete the existing method, This round and round has pushed the problem back to square one. Since there is only one method in the interface, we can abstract the method in the interface into a single “do” operation, which can be described by the class name, and we are back on track with the design.

    At this point, all our interface has to do is perform an action, and the remote control doesn’t even need to know what the action is, because the remote control provides a setting method that only knows that the class in which it is set supports “execute.” At this stage, I think we’ve reached a point where the design is flexible enough to accommodate future changes, at least for remote controls.

  3. The part that needs decoupling

    In the previous section, we looked at the scenario problem from the perspective of change. Now we will describe it from the decoupling section, where the previous design may often be presented, but from a different perspective.

    Speaking of decoupling, for these scenarios, let’s think about which roles and which interactions need to be decoupled. The answer is remote controls and appliances. If designed in a simple way, remote control code, have to be different for different electrical calls, will produce direct dependencies, so we are programming for the realization of different appliances, is not in conformity with the dependency inversion principle, but this principle also gave a solution, we should be aiming at its abstract programming, the introduction of the front part of the “adapter”, It perfectly conforms to the principle of dependency inversion, which separates this part of dependency.

    Looking at the previous part of the UML class diagram, it seems that the command pattern also introduces another coupling, the client scenario associated with the Receiver class? In fact, this part is to rely on the “adapter” class in front of the design (start from here, we will call this so-called “adapter” classes for the command), we will control electrical operation encapsulated into the command class, can have two choices, one is to appliances direct coupled into the command class, now command classes and electric coupling, This section is also about coupling, so shouldn’t the coupling here be fixed? Should be considered when we design code, is the need to actual needs, the coupling and decoupling of this part of the coupling is needed to be solved, because it was unable to cope with changes in the future, but here we encounter command class of electrical coupling, we can be thought of as the aforementioned “changes need to be isolated” part, its future changes are not directly modify, We extend it by creating new command classes, so this encapsulates the changing role that must be programmed for the implementation throughout the design model, and we can’t be fooled. Just tells the story of the first option, and the second option is to appliances injection by means of dependency injection to the command class, make the command class can be controlled by the dynamic encapsulation appliances, this design is, in fact, in view of the same type of electric multiple instances, while each represents no meaning or purpose. This alternative design can, in some cases, also introduce another layer of abstraction to the injected classes. At this time, the choice discussed is accompanied by the need to actively construct Receiver in the customer scenario, resulting in coupling. In fact, this part of coupling can be ignored by us, because it is “inevitable” that we directly rely on specific commands. However, in the design of some complex systems, We can also split this part of the coupling by means of a factory.

    In this case, we need to identify the parts of the design that really need to be decoupled, and the inevitable parts of the periphery that do need to be decoupled gradually as the complexity of the scene increases.

Comparison with other models

When we look at the role and interaction of the overall pattern, we can see that it is similar to other patterns, such as the policy pattern, or even almost identical if we look at the UML class diagram.

From the point of principle, and many patterns, command mode is entrusted by the way, converts the requestor’s action dynamic responder actions, but the main is the starting point of design, different for different scenarios, this strategy model for example, we know the strategy pattern is a set of algorithms can replace each other behavior of encapsulation, Dynamic replacement can be carried out during use to complete different functions. Such a strategy mode is mainly oriented to a variety of different ways of realizing a single goal, and its goal is generally the same, but the behavior process of realizing this goal is different in the middle. The command mode is for different purposes and provides different behavior processes for each purpose. Another difference is the completeness of the overall request interaction: In running a request, the command classes encapsulate the behavior of the basic equal to all actions (macros is actually, please don’t be confused), and is not necessarily a request all the strategy, and as a part, such as in a message strategy design, message sending types and transport protocols are designed to replace the strategy, Thus the send type policy and transport protocol policy are only part of the overall request process.

In fact, the existing design mode concentration, there are a lot of very similar design mode on the surface, but its starting point is very different, because of the different starting point, the behavior and function of the way of expansion in practical application will foil it more and more obvious, until people suddenly understand.

Other chatter in command mode

The previous scenario examples are basically matching the roles described in UML class diagrams, and the characteristics that can be explained are relatively monotonous. In current practice, command mode has some nice extensions, such as task queues: Queues as Invoker roles, tasks as command roles, each task is saved in queues by some special mechanism, executed in any possible way at any possible time, and even supports setting dependencies between tasks (such as NSOperation in the Apple Foundation framework). Personally, I think this design is very representative of the command pattern.

3. The conclusion

The above content is some thoughts or supposition generated when I was learning command mode. Since it is the first one, it is relatively wordy, and its purpose is mainly to record my thoughts. If you find any mistakes in my thinking, I hope you can point out and communicate with me.

The original address: www.notion.so/nakahira/e8…

4. Reference materials

Head First Design Patterns