Document directory
- Controller implementation
- Class Design
- Code Implementation
- Problems
- New Design Scheme
- Code Implementation
- Problems
- Problem Analysis
- Command type implementation
- Code Implementation
PDF download: http://www.tracefact.net/Document/Command.pdf
Command mode Step by step introduction
Speaking of the Command mode, I think there is nothing better to illustrate than the remote control example. This article will use it to implement the GOF Command mode step by step.
Let's take a look at the requirements of this remote control program: if we need to design a remote control for the electrical appliances at home, we can use this controller to control electrical appliances (such as lights, fans, air conditioners, etc). Our controller has a series of buttons corresponding to an electric appliance in the house. When we press "On the remote control, the electric appliance is turned On. When we press" Off, turn off the appliance.
Now, let's get started with the Command mode.
Implementation of the HardCoding Controller
Generally, there are two ways to consider the problem: consider the most complex situation, that is, to think as far as possible, and consider the maintainability and scalability of the program at the beginning of the design; there is also the simplest case. If we do not consider expansion or customer requirements, we will do what we need and what new requirements will be available in the future. Of course, the two methods have their own advantages and disadvantages. In this article, we will start from the simplest situation.
Let us assume that the controller can only control three electrical appliances: Lights, fans, and doors (you should be an electronic door ^ ). There should be three groups of controllers, with six buttons in total. Each group has the "On" and "Off" Buttons respectively. At the same time, we stipulate that the first group of buttons corresponds to the lamp, the second group of buttons corresponds to the fan, and the third group corresponds to the door. The Controller should be like this:
Class Design
Well, the Controller looks like this. What are the lights, fans, and doors like? If you have read the preceding models, you may think that you need to create a base class or interface for them to inherit or implement. Now let's take a look at what the appliances we want to control look like:
Sorry, but unfortunately,Their interfaces are completely different.We have no way to abstract them, but because we only consider the most primitive requirements of our customers (the simplest case) at the moment, we can directly combine them into the ControlPanel) medium
NOTE:An interface has a narrow meaning: it is the type of an interface declared. There is also a broad definition: it refers to the methods and attributes that an object exposes to the outside world. Therefore, an abstract class can also be called an interface. Here, their interfaces are different, which means that the methods for exposing these three appliances to the outside world are completely different.
Note that the PressOn method indicates that a key is pressed and an int-type parameter is accepted: SlotNo, which indicates that the first key is pressed. Obviously, the value of SlotNo is 0 to 2. PressOff is exactly the same design.
Code Implementation
Namespace Command {
// Define the lamp
Public class Light {
Public void TurnOn (){
Console. WriteLine ("The light is turned on .");
}
Public void TurnOff (){
Console. WriteLine ("The light is turned off .");
}
}
// Define the fan
Public class Fan {
Public void Start (){
Console. WriteLine ("The fan is starting .");
}
Public void Stop (){
Console. WriteLine ("The fan is stopping .");
}
}
// Define the door
Public class Door {
Public void Open (){
Console. WriteLine ("The door is open for you .");
}
Public void Shut (){
Console. WriteLine ("The door is closed for safety ");
}
}
// Define the remote control
Public class ControlPanel {
Private Light light;
Private Fan fan;
Private Door;
Public ControlPanel (Light light, Fan fan, Door ){
This. light = light;
This. fan = fan;
This. door = door;
}
// Click the On button. SlotNo, which of the following buttons is pressed
Public void PressOn (int slotNo ){
Switch (slotNo ){
Case 0:
Light. TurnOn ();
Break;
Case 1:
Fan. Start ();
Break;
Case 2:
Door. Open ();
Break;
}
}
// Click the Off button.
Public void PressOff (int slotNo ){
Switch (slotNo ){
Case 0:
Light. TurnOff ();
Break;
Case 1:
Fan. Stop ();
Break;
Case 2:
Door. Shut ();
Break;
}
}
}
Class Program {
Static void Main (string [] args ){
Light light = new Light ();
Fan fan = new Fan ();
Door door = new Door ();
ControlPanel panel = new ControlPanel (light, fan, door );
Panel. PressOn (0); // press the first On button and the light is turned On
Panel. PressOn (2); // press the second On button and the door is opened.
Panel. PressOff (2); // press the second Off button and the door is closed.
}
}
}
Output:
The light is turned on.
The door is open for you.
The door is closed for safety
Problems
Although this solution can solve the current problem, it has almost no scalability. In other words, callers (lamps, fans, and doors) are tightly coupled with their callers (Invoker: Remote Control. The remote control not only needs to know exactly which appliances can be controlled, but also the methods by which these appliances can be called.
- If we need to change the order of the electrical appliances controlled by the buttons, for example, we need to let button 1 not control the lamp, but control the door, then we need to modify the Switch statement in the PressOn and PressOff methods.
- If we need to add a button to the remote control to control an electric appliance, the fields, constructors, PressOn, and PressOff methods of the remote control must be modified.
- If we don't add a button to the remote control, but require that it be able to control 10 or electrical appliances, in other words, we can dynamically allocate a button to control which electrical appliance, this design seems to be impossible.
Another new design of HardCoding
Before considering the new scheme, let's review the previous design. The third question seems to imply that our remote control is not good enough. Think about it and we find that we can design the remote control like this:
By comparison, we can see that we can control the electric appliance controlled by the remote control through the valve that can be moved up and down on the left side (according to the figure, the lamp is controlled). After selecting the valve, we can use the On and Off buttons to control the electrical appliance. At this point, we need to add another method to control the valve (and then select the desired electrical appliance ). We call this method SetDevice (). Then our design becomes as follows:
NOTE:In the figure, and in the real world, the number of electrical appliances that the valve can control is always limited, but in the program, it can be infinite. It depends on how many electrical appliances you have such as light.
Notes:
- Because we assume that the remote control can control an infinite number of electrical appliances, so we cannot specify the specific electrical appliance type here, because in C #, all types are inherited from the Object, we will set SetDevice () the parameters accepted by the method are set as objects.
- ControlPanel does not know which class it will control, so the ControlPanel is not associated with Light, Door, and Fan.
- The PressOn () and PressOff () Methods no longer require parameters, because obviously there is only one set of On and Off buttons.
Code Implementation
Namespace Command {
Public class Light {// omitted}
Public class Fan {// omitted}
Public class Door {// omitted}
// Define the remote control
Public class ControlPanel {
Private Object device;
// Click the On button.
Public void PressOn (){
Light light = device as Light;
If (light! = Null) light. TurnOn ();
Fan fan = device as Fan;
If (fan! = Null) fan. Start ();
Door door = device as Door;
If (door! = Null) door. Open ();
}
// Click the Of button.
Public void PressOff (){
Light light = device as Light;
If (light! = Null) light. TurnOff ();
Fan fan = device as Fan;
If (fan! = Null) fan. Stop ();
Door door = device as Door;
If (door! = Null) door. Shut ();
}
// Set the electric appliance controlled by the valve
Public void SetDevice (Object device ){
This. device = device;
}
}
Class Program {
Static void Main (String [] args ){
Light light = new Light ();
Fan fan = new Fan ();
ControlPanel panel = new ControlPanel ();
Panel. SetDevice (light); // set the valve control light
Panel. PressOn (); // turn on the light
Panel. PressOff (); // turn off the light
Panel. SetDevice (fan); // sets the valve control fan.
Panel. PressOn (); // open the door
}
}
}
Problems
First, we can see that this solution seems to solve most of the problems of the first design, except for a few flaws:
- Although we can control any number of devices, we still need to modify the PressOn () and PressOff () Methods every time we add a controllable device.
- In the PressOn () and PressOff () methods, type conversion is required for all the appliances that may be controlled, which is undoubtedly inefficient.
Encapsulation call Problem Analysis
We seem to be unable to cope with the situation and cannot think of a better solution. At this time, let's look back at the ControlPanel's PressOn () and PressOff () code.
// Click the On button.
Public void PressOn (){
Light light = device as Light;
If (light! = Null) light. TurnOn ();
Fan fan = device as Fan;
If (fan! = Null) fan. Start ();
Door door = device as Door;
If (door! = Null) door. Open ();
}
We found that the PressOn () and PressOff () methods need to be modified each time a new device is added,The actual change is the call to the object method, because no matter how many if statements there are, only one method of one of the objects not null will be called.Then let's review the idea of OO, Encapsulate what varies (encapsulation changes ). Should we try to encapsulate this change (method call?
Before considering how to encapsulate the class, we suppose there is already a class that encapsulates it. We call this class Command. How can this class be used?
Let's first consider its composition, because it needs to encapsulate the methods of each object, so it should expose a method, which can represent light. turnOn () can also represent a fan. start (), which can also represent door. open (), let's name this method, called Execute ().
Now we have the Command class and the Execute () method of 10 thousand gold oil. Now, we modify the PressOn () method, use this Command class to control electrical appliances (call methods of various classes ).
// Click the On button.
Public void PressOn (){
Command. Execute ();
}
Wow, isn't it too simple !? But it is so simple, but we still found two problems:
- Command should be able to know which method of the electrical class it calls, which implies that the Command class should save a reference for a specific electrical class.
- Our ControlPanel should have two commands. One Command corresponds to all enabled operations (called onCommand), and the other Command corresponds to all closed operations (called offCommand ).
At the same time, our SetDevice (object) method should also be changed to SetCommand (onCommand, offCommand ). Now let's take a look at the panoramic chart of the new ControlPanel.
Command type implementation
Obviously, we should be able to see that the onCommand object variables (instance variable) and offCommand variables belong to the Command type. At the same time, we have discussed above that the Command class should have an Execute () method, in addition, it also needs to be able to save the reference to each object, through the Execute () method can call its reference object method.
Then, based on this idea, let's take a look at what the Command object of the light Operation (call the TurnOn () method of the light object) should look like:
Public class LightOnCommand {
Light light;
Public Command (Light light ){
This. light = light;
}
Public void Execute (){
Light. TurnOn ();
}
}
Let's look at what the Command object of the fan opening (calling the Start () method of the fan object) should look like:
Public class FanStartCommand {
Fan fan;
Public Command (Fan fan ){
This. fan = fan;
}
Public void Execute (){
Fan. Start ();
}
}
This obviously does not work, and it does not solve any problems, because FanStartCommand and LightOnCommand are of different types, our ControlPanel requires that only one type of Command be accepted for all open operations. However, as discussed above, we already know that all commands have an Execute () method. Why don't we define an interface to solve this problem?
OK. Now we have completed all the design. Let's take a look at the final UML diagram and implement the Code (for simplicity, only the lights and fans are added ).
Let's take a look at what this figure shows and the sequence of occurrence:
- ConsoleApplication, which is our application. It creates an electrical Fan, Light object, LightOnCommand, and FanStartCommand.
- LightOnCommand and FanStartCommand implement the ICommand interface, which stores references to the Fan and Light and calls the Fan and Light methods through Execute.
- ControlPanel integrates the Command object. by calling the Execute () method of Command, it indirectly calls the TurnOn () method of Light or the Stop () method of Fan.
The sequence diagram between them is as follows:
It can be seen that by introducing the Command object, ControlPanel knows nothing about the Fan or Light object it actually calls. It only knows that when On is pressed, The Execute () method of onCommand is called; when Off is pressed, The Execute () method of offCommand is called. Light and Fan certainly do not know who is calling it. In this way, we can decouple the callers (Invoker, remote control ControlPanel) and the callers (fans, fans, etc. If we need to extend the ControlPanel in the future, we only need to add an object that implements the ICommand interface. No modification is required for the ControlPanel.
Code Implementation
Namespace Command {
// Define the air conditioner to test the new control type for the remote control.
Public class AirCondition {
Public void Start (){
Console. WriteLine ("The AirCondition is turned on .");
}
Public void SetTemperature (int I ){
Console. WriteLine ("The temperature is set to" + I );
}
Public void Stop (){
Console. WriteLine ("The AirCondition is turned off .");
}
}
// Define the Command INTERFACE
Public interface ICommand {
Void Execute ();
}
// Define the on-air-conditioning command
Public class AirOnCommand: ICommand {
AirCondition airCondition;
Public AirOnCommand (AirCondition airCondition ){
This. airCondition = airCondition;
}
Public void Execute () {// note that you can add multiple methods in Execute ()
AirCondition. Start ();
AirCondition. SetTemperature (16 );
}
}
// Define the command for shutting down the air conditioner
Public class AirOffCommand: ICommand {
AirCondition airCondition;
Public AirOffCommand (AirCondition airCondition ){
This. airCondition = airCondition;
}
Public void Execute (){
AirCondition. Stop ();
}
}
// Define the remote control
Public class ControlPanel {
Private ICommand onCommand;
Private ICommand offCommand;
Public void PressOn (){
OnCommand. Execute ();
}
Public void PressOff (){
OffCommand. Execute ();
}
Public void SetCommand (ICommand onCommand, ICommand offCommand ){
This. onCommand = onCommand;
This. offCommand = offCommand;
}
}
Class Program {
Static void Main (String [] args ){
// Create a remote control object
ControlPanel panel = new ControlPanel ();
AirCondition airCondition = new AirCondition (); // create an air conditioner object
// Create a Command object to pass the air conditioner object
ICommand onCommand = new AirOnCommand (airCondition );
ICommand offCommand = new AirOffCommand (airCondition );
// Set the remote control Command
Panel. SetCommand (onCommand, offCommand );
Panel. PressOn (); // press the On button to turn On the air conditioner. The temperature is adjusted to 16 degrees.
Panel. PressOff (); // press the Off button to turn Off the air conditioner
}
}
}
Command mode
In fact, all we have done above implements another design mode: Command mode. Now it's time to give an official definition. I don't know how to write this part every time. There are too many people to write and too many materials. I believe you know the Command mode clearly here, so I should keep it simple.
Formal Definition of Command mode:Encapsulate a request as an object so that you can parameterize the customer with different requests, queue requests or record request logs, and support unrecoverable operations.
Its static diagram is as follows:
Its sequence diagram is as follows:
We can compare them with the previous figure. For these two figures, they are basically not changed except for a new name. I will not describe them any more and leave you some room for thinking.
Summary
This article briefly introduces the GOF Commmand mode. We have implemented this mode through a simple example of the Home Appliance Remote Control.
We first learned how to implement the HardCoding method without this mode, discussed its shortcomings, and then changed another improved implementation method, I discussed its shortcomings again. Then, we cleverly completed the design by encapsulating the call of an object into a Command object. Finally, we provide a formal definition of the Command mode.
This article briefly introduces the Command mode. Its advanced applications include UnDo, Transaction, and Queuing Request.
I hope this article will help you!