命令模式(head first 設計模式5)


一、命令模式定義

命令大家都不會陌生,那么在開始命令模式之前,可以想象一下生活中的命令模式的特點:

如老板命令你完成一個OA項目是一個命令,接着看看其特點:

1、在上面的命令中,命令的執行者肯定是聰明的你了。具體的執行方法,可能是通過vs實現,或者是通過eclipse實現,由此看來:命令要有個命令的執行者,還要有個命令的執行方法。

2、命令的發出者很明顯是老板,老板還有個發出方法,可能是通過電話給你說,也可能給你郵件給你說,也可能是通過開會給你說。所以命令的發出者要有一個命令,還要有個發出的方法。

3、最后看看命令,命令有個名字,命令的肯定要執行。而且命令是在boss給你發出通知后執行的。

接下來看看命令模式的定義:

命令模式:將請求封裝成對象,以便使用不同的請求、日志、隊列等來參數化其他對象。命令模式也支持撤銷操作。

每次講一個模式時,從定義都不能體會其中的技巧,所以接着我會通過舉例子來說明命令模式。

二、命令模式的舉例

下面來看看多用遙控器是如何使用命令模式的。

2.1需求

假設某個公司需要設計一個多用功能的遙控器。基本的需求如下:

該遙控器有可以控制風扇,白熾燈,熱水器等等的多對開關,而且可能還有其他的電器,暫時不做其功能,但是希望可以保留接口,用的時間可以方便的擴展。

除上面的需求之外,還需要有個按鈕,可以撤銷上一步的操作。基本功能如下圖:

image

2.2問題

在設計遙控器時,風扇,白熾燈,熱水器的開關方法已經定義好,其名字各不相同。不妨設置其方法為如下:

image

由於各種電器的開關方法都不一樣,而且還存在一個待擴展的電器,如果沒有學習命名模式之前,我們在設置擴展的開關時,會出現的問題是什么呢?假設現在有電視,冰箱還可能會用到遙控器,那么我們會在最后一個開關上寫if else,當然如果哪一天有多了一個大門也加入了我們的遙控的行列,這樣我們繼續加if else ,很顯然隨着電器的高速發展,會有多個需要遙控可以控制的。

舉個例子,如果我們是需要遙控的客戶,現在有一款遙控如果有遙控可以進行擴展,一種是可以擴展指定類型的,像上面的,只能再去擴展電視和冰箱中的一種,偶爾有一天你看到隔壁鄰居的門,也可以使用遙控了,所以你去把你的高級遙控器,拿到擴展店時,擴展工程師說了,現在只能擴展電視和冰箱,不支持對大門的遙控擴展.

我們肯定是希望,可以自由的擴展,大門可以使用遙控了,就對大門擴展,車門使用遙控了,就對車門擴展……其實也就是一種松耦合的實現。

2.3分析問題

為了實現松耦合,我們現在來想一下,周末去請朋友吃飯,服務員mm問你吃什么,你說水煮活魚,然后在菜單上面,寫上水煮活魚。下個星期天想吃花生米啤酒,同樣也寫在訂單上,然后服務員mm把訂單拿給廚師。

在上面的例子中,無論你點了什么菜,服務員mm,只需要知道顧客點的什么菜,從而給不同的廚師做(雖然不是直接的,但最終涼菜會給涼菜的師傅做,熱菜的會給熱菜的廚師做),然而具體的怎么做是廚師的事情,服務員知道顧客點上面菜,只是為了能正確的交個廚師去做。

其實在這個例子中,也是一個命令模式的例子,不同的訂單對應的有不同的廚師,最終訂單拿到廚師面前,就是對廚師下了個命令,要做菜了。每訂單或者說是每種菜都對應着自己的廚師,所以,客服需要有訂單的的引用,訂單為了知道調用哪個廚師,需要有廚師引用;兩個引用都是使用的組合。從而可以調用多種自己需要對象的方法來實現松耦合。這里記住:客服mm知道顧客點菜的目的是為了讓不同的廚師去做。再直接一些就是為了使用不同的命令。

上面的吃飯問題和我們的遙控器問題差不多,都是包含下命令,命令的執行者,以及命令的具體內容。如果還是有些不清楚的話,就用簡單的程序模擬一下上面的過程:

 1 class Program 
 2 { 
 3      static void Main(string[] args) 
 4      { 
 5          MM mm = new MM(); 
 6          //想吃熱菜 
 7          mm.SetOrder(new ReCaiOrder()); 
 8          //mm還需要把菜單拿到廚師那里哦 
 9          mm.OnOrder(); 
10          //想吃涼菜 
11          mm.SetOrder(new LiangCaiOrder()); 
12          mm.OnOrder(); 
13          Console.ReadKey(); 
14      } 
15 }
16 
17 /// <summary> 
18 /// 訂單 
19 /// </summary> 
20 interface IOrder 
21 { 
22      void Excute(); 
23 }
24 
25 /// <summary> 
26 /// 涼菜做法 
27 /// </summary> 
28 class LiangCaiChuShi 
29 { 
30      public void MakeCook() 
31      { 
32          Console.WriteLine("涼菜~!!!"); 
33      } 
34 } 
35 /// <summary> 
36 /// 涼菜訂單 
37 /// </summary> 
38 class LiangCaiOrder:IOrder 
39 { 
40      LiangCaiChuShi chushi=new LiangCaiChuShi(); 
41      public void Excute() 
42      { 
43          chushi.MakeCook(); 
44      } 
45 } 
46 /// <summary> 
47 /// 熱菜做法 
48 /// </summary> 
49 class ReCaiChuShi 
50 { 
51      public void Cook() 
52      { 
53          Console.WriteLine("熱菜!!"); 
54      } 
55 }
56 
57 /// <summary> 
58 /// 熱菜訂單 
59 /// </summary> 
60 class ReCaiOrder : IOrder 
61 { 
62      ReCaiChuShi chushi=new ReCaiChuShi(); 
63      public void Excute() 
64      { 
65          chushi.Cook(); 
66      } 
67 } 
68 class MM 
69 { 
70      IOrder order; 
71      public void SetOrder(IOrder order) 
72      { 
73          this.order = order; 
74      } 
75      public void OnOrder() 
76      { 
77          order.Excute(); 
78      } 
79 } 
View Code

上面的例子中,廚師的做法有可能不同,就像我們遙控器的開關一樣,電器的開關方法不一定相同。如果要執行,只需要把這個方法包在命令的激發方法中,然后去調用具體的方法就可以了。

盡管上面的例子有些牽強,但是還是模擬了命令的過程,為我們遙控器的設計提供了思路。

2.4解決問題

回到我們的遙控器問題上面來,我們可以先定義好我們的風扇,白熾燈,熱水器。然后定義其分別的開關命令,每個命令都有自己對應的電器引用,而且會在命令的Excute中包裝電器的開或者關,最后需要把命令安裝到遙控器上面,在遙控器上每個按鈕都對應有自己的激發方法,其代碼如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6 
  7 namespace RemoteControl
  8 {
  9     class Program
 10     {
 11         static void Main(string[] args)
 12         {
 13             //家中的電器
 14             Fan fan=new Fan();
 15             Light light=new Light();
 16             Heater heater=new Heater();
 17 
 18             //電器分別對應的命令
 19             FanOffCommand fanOffCommand=new FanOffCommand(fan);
 20             FanOnCommand fanOnCommand=new FanOnCommand(fan);
 21             LightOnCommand lightOnCommand=new LightOnCommand(light);
 22             LightOffCommand lightOffCommand=new LightOffCommand(light);
 23             HeaterOnCommand heaterOnCommand=new HeaterOnCommand(heater);
 24             HeaterOffCommand heaterOffCommand=new HeaterOffCommand(heater);
 25             RemoteControl remoteControl = new RemoteControl();
 26 
 27             //設置遙控器
 28             remoteControl.SetCommand(0, fanOnCommand, fanOffCommand);
 29             remoteControl.SetCommand(1, lightOnCommand, lightOffCommand);
 30             remoteControl.SetCommand(2, heaterOnCommand, heaterOffCommand);
 31             //分別測試遙控器的命令
 32             remoteControl.OnButtonWasPress(1);
 33             remoteControl.OffButtonWasPress(1);
 34             remoteControl.OnButtonWasPress(0);
 35             remoteControl.OffButtonWasPress(0);
 36             Console.ReadKey();
 37         }
 38     }
 39 
 40     /// <summary>
 41     /// 風扇類
 42     /// </summary>
 43    public class Fan
 44     {
 45         public void FanOn()
 46         {
 47             Console.WriteLine("風扇開了");
 48         }
 49         public void FanOff()
 50         {
 51             Console.WriteLine("風扇關了");
 52         }
 53     }
 54     /// <summary>
 55     /// 燈類
 56     /// </summary>
 57    public class Light
 58    {
 59        public void LightOn()
 60        {
 61            Console.WriteLine("燈亮了");
 62        }
 63        public void LightOff()
 64        {
 65            Console.WriteLine("燈滅了");
 66        }
 67    }
 68     /// <summary>
 69     /// 熱水器類
 70     /// </summary>
 71    public class Heater
 72    {
 73        public void HeaterOn()
 74        {
 75            Console.WriteLine("加熱中");
 76        }
 77        public void HeaterOff()
 78        {
 79            Console.WriteLine("停止加熱");
 80        }
 81    }
 82 
 83     /// <summary>
 84     /// 命令接口
 85     /// </summary>
 86    public interface ICommand
 87    {
 88        void Excute();
 89    }
 90 
 91    public class FanOnCommand : ICommand
 92    {
 93        Fan fan;
 94        public FanOnCommand(Fan fan)
 95        {
 96            this.fan = fan;
 97        }
 98         public void Excute()
 99         {
100             this.fan.FanOn();
101         }
102     }
103 
104    public class FanOffCommand : ICommand
105    {
106        Fan fan;
107        public FanOffCommand(Fan fan)
108        {
109            this.fan = fan;
110        }
111         public void Excute()
112         {
113             this.fan.FanOff();
114         }
115    }
116 
117    public class LightOnCommand : ICommand
118    {
119        Light light;
120        public LightOnCommand(Light light)
121        {
122            this.light = light; 
123        }
124        public void Excute()
125        {
126            light.LightOn();
127        }
128 
129    }
130 
131    public class LightOffCommand : ICommand
132    {
133        Light light;
134        public LightOffCommand(Light light)
135        {
136            this.light = light;
137        }
138        public void Excute()
139        {
140            this.light.LightOff();
141        }
142    }
143 
144    public class HeaterOnCommand : ICommand
145    {
146        Heater heater;
147        public HeaterOnCommand(Heater heater)
148        {
149            this.heater = heater;
150        }
151        public void Excute()
152        {
153            this.heater.HeaterOn();
154        }
155    }
156 
157    public class HeaterOffCommand : ICommand
158    { 
159        Heater heater;
160        public HeaterOffCommand(Heater heater)
161        {
162            this.heater = heater;
163        }
164        public void Excute()
165        {
166            this.heater.HeaterOff();
167        }
168    }
169 
170    public class NoCommand : ICommand
171    {
172        public void Excute()
173        { }
174    }
175 
176    public class RemoteControl
177    {
178       private ICommand[] onCommands;
179       private ICommand[] offCommands;
180       public RemoteControl()
181       {
182          ICommand noCommand=new NoCommand();
183           onCommands = new ICommand[4];
184           offCommands = new ICommand[4];
185           for (int i = 0; i < 4; i++)
186           {
187               onCommands[i] = noCommand;
188               offCommands[i] = noCommand;
189           }
190       }
191 
192       public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
193       {
194           onCommands[slot] = onCommand;
195           offCommands[slot] = offCommand;
196       }
197       public void OnButtonWasPress(int slot)
198       {
199           onCommands[slot].Excute();
200       }
201       public void OffButtonWasPress(int slot)
202       {
203           offCommands[slot].Excute();
204       }
205      
206    }
207 }
208     
View Code

這樣基本上就實現了我們的現有的三種電器的遙控。需要注意的是,在開始初始化遙控器時,對每個命令初始化成了NoCommand,也就是什么都不執行。在命令的初始化經常使用,同時這也解決了我們的在擴展前什么都不做的難題。看了風扇,白熾燈,熱水器的遙控實現,進一步的擴展任何的電器,相信都不是什么難事。但是還有個功能沒有實現,就是撤銷到上一步的操作,接下來我們就來實現撤銷操作。

2.5問題的補充說明

撤銷操作就想我們遙控中的返回一樣。基本上就是燈亮着,突然按了一下關燈,然后再按一下返回鍵,燈就亮了。其他的電器同樣的道理。下面先看一下燈的撤銷原理,命令除了執行外還有一個撤銷,所以我們需要先都命令的接口添加一個方法。

/// <summary>
/// 命令接口
/// </summary>
public interface ICommand
{
    void Excute();
    void Undo();
}

對於開燈需要做的修改如下:

public class LightOnCommand : ICommand
   {
       Light light;
       public LightOnCommand(Light light)
       {
           this.light = light;
       }
       public void Excute()
       {
           light.LightOn();
       }
       /// <summary>
       /// 調用命令的反命令
       /// </summary>
       public void Undo()
       {
           light.LightOff();
       }
   }

其他命令同理,代碼會在源碼中一並給出。也就是每個命令都有自己的反命令,在Undo方法里面也就是調用反命令的Excute方法。每當按下一個按鈕時,就去記錄其命令的名稱,如果按撤銷的話,就執行命名的Undo方法。下面給出主要代碼:

public void OnButtonWasPressed(int slot)
   {
       onCommands[slot].Excute();
       backCommand=onCommands[slot];
   }
   public void OffButtonWasPressed(int slot)
   {
       offCommands[slot].Excute();
       backCommand = offCommands[slot];
   }
   public void BackButtonWasPressed()
   {
       backCommand.Undo();
   }

以上是對遙控器對命令的撤銷,需要注意兩點1、通過記住命令執行之前的狀態,然后去恢復到原來的狀態。2、在每次執行之后要記住執行的那個命令。也即記住命令和記住狀態。

除了一次執行一個命令和撤銷一個命令,當然還可以一次執行多個命令。下面給出主要代碼:

public class MutlipleCommand : ICommand
   {
       ICommand[] commands;
       ICommand[] backCommands;
       public MutlipleCommand(ICommand[] commands)
       {
           this.commands = commands;
           backCommands = new ICommand[commands.Length];
       }


       public void Excute()
       {
           for (int i = 0; i < commands.Length; i++)
            {
              commands[i].Excute();
              backCommands[i] = commands[i];
            }
          
       }

       public void Undo()
       {
           for (int i = 0; i < commands.Length; i++)
           {
               backCommands[i].Undo();
           }
       }
   }

三、命令模式類圖

image

四、總結

命令模式主要通過中介Command實現了發出命令者和命令的執行者,也即Invoke類和Receiver的松耦合。本文先給出了命令模式的定義,通過吃飯的例子給出了使用命令模式實現遙控器設計思路,最后還提到了撤銷命令和一個命令實現多個命令的做法。

源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM