一、命令模式定義
命令大家都不會陌生,那么在開始命令模式之前,可以想象一下生活中的命令模式的特點:
如老板命令你完成一個OA項目是一個命令,接着看看其特點:
1、在上面的命令中,命令的執行者肯定是聰明的你了。具體的執行方法,可能是通過vs實現,或者是通過eclipse實現,由此看來:命令要有個命令的執行者,還要有個命令的執行方法。
2、命令的發出者很明顯是老板,老板還有個發出方法,可能是通過電話給你說,也可能給你郵件給你說,也可能是通過開會給你說。所以命令的發出者要有一個命令,還要有個發出的方法。
3、最后看看命令,命令有個名字,命令的肯定要執行。而且命令是在boss給你發出通知后執行的。
接下來看看命令模式的定義:
命令模式:將請求封裝成對象,以便使用不同的請求、日志、隊列等來參數化其他對象。命令模式也支持撤銷操作。
每次講一個模式時,從定義都不能體會其中的技巧,所以接着我會通過舉例子來說明命令模式。
二、命令模式的舉例
下面來看看多用遙控器是如何使用命令模式的。
2.1需求
假設某個公司需要設計一個多用功能的遙控器。基本的需求如下:
該遙控器有可以控制風扇,白熾燈,熱水器等等的多對開關,而且可能還有其他的電器,暫時不做其功能,但是希望可以保留接口,用的時間可以方便的擴展。
除上面的需求之外,還需要有個按鈕,可以撤銷上一步的操作。基本功能如下圖:
2.2問題
在設計遙控器時,風扇,白熾燈,熱水器的開關方法已經定義好,其名字各不相同。不妨設置其方法為如下:
由於各種電器的開關方法都不一樣,而且還存在一個待擴展的電器,如果沒有學習命名模式之前,我們在設置擴展的開關時,會出現的問題是什么呢?假設現在有電視,冰箱還可能會用到遙控器,那么我們會在最后一個開關上寫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 }
上面的例子中,廚師的做法有可能不同,就像我們遙控器的開關一樣,電器的開關方法不一定相同。如果要執行,只需要把這個方法包在命令的激發方法中,然后去調用具體的方法就可以了。
盡管上面的例子有些牽強,但是還是模擬了命令的過程,為我們遙控器的設計提供了思路。
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
這樣基本上就實現了我們的現有的三種電器的遙控。需要注意的是,在開始初始化遙控器時,對每個命令初始化成了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();
}
}
}
三、命令模式類圖
四、總結
命令模式主要通過中介Command實現了發出命令者和命令的執行者,也即Invoke類和Receiver的松耦合。本文先給出了命令模式的定義,通過吃飯的例子給出了使用命令模式實現遙控器設計思路,最后還提到了撤銷命令和一個命令實現多個命令的做法。