前言:命令模式我們平??赡軙?huì)經(jīng)常使用,如果我們不了解命令模式的結(jié)構(gòu)和定義那么在使用的時(shí)候也不會(huì)將它對(duì)號(hào)入座。

          舉個(gè)例子:在winform開發(fā)的時(shí)候我們常常要用同一個(gè)界面來進(jìn)行文件的下載,但是并不是所有地方都用同一個(gè)下載邏輯處理文件,然后下載界面卻可以是同一個(gè)界面。


          為了以后復(fù)用下載界面(下載顯示,進(jìn)度條等)我們常常將下載執(zhí)行操作定義成一個(gè)接口,在具體使用的時(shí)候?qū)崿F(xiàn)接口,將具體執(zhí)行對(duì)象設(shè)置到下載界面。當(dāng)下載按鈕被按下的時(shí)候,就調(diào)用設(shè)置的具體執(zhí)行對(duì)象(接收者)來執(zhí)行下載的處理。

          那接下來我們就看下命令模式的具體細(xì)節(jié)和實(shí)現(xiàn),再回頭想想我們平時(shí)什么時(shí)候不經(jīng)意就使用到了命令模式,這樣以后交流使用專業(yè)的術(shù)語不僅能裝還能用。

          1、遙控器應(yīng)用場(chǎng)景

          HeadFirst設(shè)計(jì)模式一書中以遙控器為例實(shí)現(xiàn)命令模式,以餐館點(diǎn)餐講解命令模式的對(duì)象和結(jié)構(gòu)。為了邏輯清晰我們不混合兩種講解方式,只以遙控器為例講解。


          現(xiàn)在需求是有一個(gè)遙控器,遙控器上面有控制各種電器的開關(guān),而開關(guān)的執(zhí)行控制電器是由各個(gè)廠家開發(fā)的設(shè)備(對(duì)象)插入到對(duì)應(yīng)開關(guān)位置的卡槽里面,基于這些條件我們來實(shí)現(xiàn)遙控器系統(tǒng)。

          簡(jiǎn)單粗暴的解決方案可以對(duì)開關(guān)做一個(gè)標(biāo)識(shí),當(dāng)某個(gè)開關(guān)被按下時(shí)根據(jù)開關(guān)類型進(jìn)行if判斷。形如 if slot1==Light ,then light.on(),
          else if slot1==Tv then tv.on()
          這種代碼將出現(xiàn)一堆,對(duì)于以后增加減少開關(guān)或者更換開關(guān)都是比較糟糕的。而對(duì)于設(shè)計(jì)遙控器類來說我們應(yīng)該讓遙控器代碼盡量保持簡(jiǎn)單,而不用去關(guān)心具體廠商類怎么執(zhí)行。所以我們應(yīng)該將執(zhí)行封裝在一個(gè)命令對(duì)象里中,那么我們就試著一步步實(shí)現(xiàn)遙控器。

            首先我們?yōu)槊顚?duì)象定義一個(gè)統(tǒng)一的接。

            接口只有一個(gè)簡(jiǎn)單的execute執(zhí)行命令方法。
          public interface Command { //執(zhí)行命令的方法 public void execute(); }
            接下來我們實(shí)現(xiàn)一個(gè)打開電燈的命令
          public class Light { public void on() { Console.WriteLine("打開電燈"); } public
          void off() { Console.WriteLine("關(guān)閉電燈"); } } public class LightOnCommand :
          Command { Light light; public LightOnCommand(Light light) { this.light = light;
          } public void execute() { light.on(); } }
            為了簡(jiǎn)單我們假設(shè)遙控器只有一個(gè)開關(guān),實(shí)現(xiàn)遙控器。
          public class SimpleRemoteControl { //卡槽 Command slot; public void
          setCommand(Command command) { slot = command; } //按下開關(guān) public void
          ButtonWasPressed() { slot.execute(); } }
            測(cè)試
          static void Main(string[] args) { SimpleRemoteControl remoteControl = new
          SimpleRemoteControl(); //廠商提供的電燈類,命令的接收者 Light light = new Light();
          //我們封裝的命令對(duì)象,設(shè)置接收者 LightOnCommand lightOnCommand = new LightOnCommand(light);
          //設(shè)置遙控器開關(guān)對(duì)應(yīng)的命令對(duì)象 remoteControl.setCommand(lightOnCommand);
          remoteControl.ButtonWasPressed(); Console.ReadKey(); }
            

          2、命令模式、類圖

          通過上面的例子我們已經(jīng)使用了命令模式來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的遙控器,再回顧【前言】我們說的界面下載文件按鈕操作是不是就是一個(gè)典型的可以使用命令模式的應(yīng)用場(chǎng)景。

          只是有一點(diǎn)我們可能不會(huì)有什么其他廠商設(shè)計(jì)好的執(zhí)行類,我們也許直接就在繼承接口的命令對(duì)象中實(shí)現(xiàn)execute的邏輯,而不用再調(diào)用其他接收者執(zhí)行。

          這就是“聰明”命令對(duì)象,上面我們實(shí)現(xiàn)的是“傻瓜”命令對(duì)象。這個(gè)稍后再說,我們先看命令模式定義和畫出類圖。

          命令模式:將“請(qǐng)求”封裝成對(duì)象,以便使用不同的請(qǐng)求、隊(duì)列或日志來參數(shù)化其他對(duì)象。命令模式也支持撤銷的操作。



          3、完成多開關(guān)遙控器和撤銷操作

          假設(shè)遙控器現(xiàn)在有五個(gè)開關(guān)。我們已經(jīng)有簡(jiǎn)單遙控器的經(jīng)驗(yàn),那么其他4個(gè)開關(guān)我們也將對(duì)應(yīng)的命令對(duì)象設(shè)置上去就行了。定義兩個(gè)數(shù)組用來記錄開關(guān)對(duì)應(yīng)的命令對(duì)象。
          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; } //按下開關(guān) public void
          OnButtonWasPressed(int slot) { onCommands[slot].execute(); } //關(guān)閉開關(guān) public void
          OffButtonWasPressed(int slot) { offCommands[slot].execute(); } //打印出數(shù)組命令對(duì)象
          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(); } }
            在遙控器中我們定義了一個(gè)Nocommand類,是為了對(duì)遙控器對(duì)應(yīng)的開關(guān)初始化命令對(duì)象,避免為空?qǐng)?bào)錯(cuò)或者消除開關(guān)調(diào)用命令對(duì)象時(shí)檢查對(duì)象是否為空的判斷。?
          public void OnButtonWasPressed(int slot) { if(onCommand[slot]!=null))
          onCommands[slot].execute(); }
            在許多設(shè)計(jì)模式中我們都能看到這種初始值或者空對(duì)象的使用。甚至有時(shí)候,空對(duì)象本身也被視為一種設(shè)計(jì)模式。(感覺這樣代碼比較優(yōu)雅O(∩_∩)O)

          遙控器完成了,我們還有做一項(xiàng)工作,就是撤銷操作。

          撤銷操作我們同樣在命令接口里面定義一個(gè)undo 方法。
          public interface Command { //執(zhí)行命令的方法 public void execute(); //撤銷命令方法 public
          void undo(); }
            然后我們讓LightOnCommand實(shí)現(xiàn)undo方法,添加LightOffCommand命令對(duì)象。
          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屬性存儲(chǔ)上一次操作。
          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; } //按下開關(guān) public
          void OnButtonWasPressed(int slot) { onCommands[slot].execute(); undoCommand =
          onCommands[slot]; } //關(guān)閉開關(guān) public void OffButtonWasPressed(int slot) {
          offCommands[slot].execute(); undoCommand = offCommands[slot]; } public void
          UndoButtonWasPressed() { undoCommand.undo(); } //打印出數(shù)組命令對(duì)象 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(); } }
          測(cè)試:



          4、補(bǔ)充總結(jié)

          補(bǔ)充:


          ①命令模式的接收者不一定要存在,之前提到過“聰明”和“傻瓜”命令對(duì)象,如果以“聰明”命令對(duì)象設(shè)計(jì),調(diào)用者和接收者之間解耦程度比不上“傻瓜”命令對(duì)象,但是我們?cè)谑褂帽容^簡(jiǎn)單的時(shí)候仍然可以使用“聰明”命令對(duì)象設(shè)計(jì)。

          ②撤銷例子我們只做了返回最后一次操作,如果要撤銷許多次我們可以對(duì)操作記錄進(jìn)行保存到堆棧,不管什么時(shí)候撤銷,我們都可以從堆棧中取出最上層命令對(duì)象執(zhí)行撤銷操作。

          命令模式常被用于隊(duì)列請(qǐng)求,日志請(qǐng)求。當(dāng)隊(duì)列按照順序取到存放的命令對(duì)象后調(diào)用執(zhí)行方法就行了而不用去管具體執(zhí)行什么。


          日志請(qǐng)求在某些場(chǎng)合可以用來將所有動(dòng)作記錄在日志中,并能在系統(tǒng)死機(jī)后通過日志記錄進(jìn)行恢復(fù)到之前的狀態(tài)(撤銷)。對(duì)于更高級(jí)的的應(yīng)用而言,這些技巧可以應(yīng)用到事務(wù)(transaction)處理中。

          ?通過簡(jiǎn)單到更進(jìn)一步的實(shí)現(xiàn)講解了命令模式和一些靈活點(diǎn)和需要注意的點(diǎn),有什么理解不到位的歡迎指正。

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                撸逼逼 | 色偷偷成人 | xxxxx日本 | 国产精品永久久久久久久久久 | 紧缚捆绑绳高潮呜呜图片 |