前言:命令模式咱們日常可能會常用,若是咱們不瞭解命令模式的結構和定義那麼在使用的時候也不會將它對號入座。設計模式
舉個例子:在winform開發的時候咱們經常要用同一個界面來進行文件的下載,可是並非全部地方都用同一個下載邏輯處理文件,而後下載界面卻能夠是同一個界面。數組
爲了之後複用下載界面(下載顯示,進度條等)咱們經常將下載執行操做定義成一個接口,在具體使用的時候實現接口,將具體執行對象設置到下載界面。當下載按鈕被按下的時候,就調用設置的具體執行對象(接收者)來執行下載的處理。ide
那接下來咱們就看下命令模式的具體細節和實現,再回頭想一想咱們平時何時不經意就使用到了命令模式,這樣之後交流使用專業的術語不只能裝還能用。測試
HeadFirst設計模式一書中以遙控器爲例實現命令模式,以餐館點餐講解命令模式的對象和結構。爲了邏輯清晰咱們不混合兩種講解方式,只以遙控器爲例講解。ui
如今需求是有一個遙控器,遙控器上面有控制各類電器的開關,而開關的執行控制電器是由各個廠家開發的設備(對象)插入到對應開關位置的卡槽裏面,基於這些條件咱們來實現遙控器系統。this
簡單粗暴的解決方案能夠對開關作一個標識,當某個開關被按下時根據開關類型進行if判斷。形如 if slot1==Light ,then light.on(), else if slot1==Tv then tv.on() 這種代碼將出現一堆,對於之後增長減小開關或者更換開關都是比較糟糕的。而對於設計遙控器類來講咱們應該讓遙控器代碼儘可能保持簡單,而不用去關心具體廠商類怎麼執行。因此咱們應該將執行封裝在一個命令對象裏中,那麼咱們就試着一步步實現遙控器。spa
首先咱們爲命令對象定義一個統一的接。設計
接口只有一個簡單的execute執行命令方法。日誌
public interface Command { //執行命令的方法 public void execute(); }
接下來咱們實現一個打開電燈的命令orm
public class Light { public void on() { Console.WriteLine("打開電燈"); } public void off() { Console.WriteLine("關閉電燈"); } } public class LightOnCommand : Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); } }
爲了簡單咱們假設遙控器只有一個開關,實現遙控器。
public class SimpleRemoteControl { //卡槽 Command slot; public void setCommand(Command command) { slot = command; } //按下開關 public void ButtonWasPressed() { slot.execute(); } }
測試
static void Main(string[] args) { SimpleRemoteControl remoteControl = new SimpleRemoteControl(); //廠商提供的電燈類,命令的接收者 Light light = new Light(); //咱們封裝的命令對象,設置接收者 LightOnCommand lightOnCommand = new LightOnCommand(light); //設置遙控器開關對應的命令對象 remoteControl.setCommand(lightOnCommand); remoteControl.ButtonWasPressed(); Console.ReadKey(); }
經過上面的例子咱們已經使用了命令模式來實現一個簡單的遙控器,再回顧【前言】咱們說的界面下載文件按鈕操做是否是就是一個典型的可使用命令模式的應用場景。
只是有一點咱們可能不會有什麼其餘廠商設計好的執行類,咱們也許直接就在繼承接口的命令對象中實現execute的邏輯,而不用再調用其餘接收者執行。
這就是「聰明」命令對象,上面咱們實現的是「傻瓜」命令對象。這個稍後再說,咱們先看命令模式定義和畫出類圖。
命令模式:將「請求」封裝成對象,以便使用不一樣的請求、隊列或日誌來參數化其餘對象。命令模式也支持撤銷的操做。
假設遙控器如今有五個開關。咱們已經有簡單遙控器的經驗,那麼其餘4個開關咱們也將對應的命令對象設置上去就好了。定義兩個數組用來記錄開關對應的命令對象。
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(); } }
在遙控器中咱們定義了一個Nocommand類,是爲了對遙控器對應的開關初始化命令對象,避免爲空報錯或者消除開關調用命令對象時檢查對象是否爲空的判斷。
public void OnButtonWasPressed(int slot) { if(onCommand[slot]!=null)) onCommands[slot].execute(); }
在許多設計模式中咱們都能看到這種初始值或者空對象的使用。甚至有時候,空對象自己也被視爲一種設計模式。(感受這樣代碼比較優雅O(∩_∩)O)
遙控器完成了,咱們還有作一項工做,就是撤銷操做。
撤銷操做咱們一樣在命令接口裏面定義一個undo 方法。
public interface Command { //執行命令的方法 public void execute(); //撤銷命令方法 public void undo(); }
而後咱們讓LightOnCommand實現undo方法,添加LightOffCommand命令對象。
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(); } }
遙控器裏面添加撤銷按鈕操做UndoButtonWasPressed並用undoCommand屬性存儲上一次操做。
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(); } }
測試:
補充:
①命令模式的接收者不必定要存在,以前提到過「聰明」和「傻瓜」命令對象,若是以「聰明」命令對象設計,調用者和接收者之間解耦程度比不上「傻瓜」命令對象,可是咱們在使用比較簡單的時候仍然可使用「聰明」命令對象設計。
②撤銷例子咱們只作了返回最後一次操做,若是要撤銷許屢次咱們能夠對操做記錄進行保存到堆棧,無論何時撤銷,咱們均可以從堆棧中取出最上層命令對象執行撤銷操做。
命令模式常被用於隊列請求,日誌請求。當隊列按照順序取到存放的命令對象後調用執行方法就好了而不用去管具體執行什麼。
日誌請求在某些場合能夠用來將全部動做記錄在日誌中,並能在系統死機後經過日誌記錄進行恢復到以前的狀態(撤銷)。對於更高級的的應用而言,這些技巧能夠應用到事務(transaction)處理中。
經過簡單到更進一步的實現講解了命令模式和一些靈活點和須要注意的點,有什麼理解不到位的歡迎指正。