一、 基本概述 問題:假設有一個控制器,該控制器上有7個可編程的插槽,每個都可以指定到一個不同的家電裝置,每個插槽都有對應的開關按鈕。這個遙控器還具備一個整體的撤銷按鈕。廠家已經提供了控制家電基本處理類。希望你能夠創建一組控制遙控器的API,讓每個插槽都能夠控制一個或一組裝置。(如下圖,廠商類) 分 ...
一、 基本概述
問題:假設有一個控制器,該控制器上有7個可編程的插槽,每個都可以指定到一個不同的家電裝置,每個插槽都有對應的開關按鈕。這個遙控器還具備一個整體的撤銷按鈕。廠家已經提供了控制家電基本處理類。希望你能夠創建一組控制遙控器的API,讓每個插槽都能夠控制一個或一組裝置。(如下圖,廠商類)
分析1:廠家提供的類,有許多都具備On()和off()方法,除此之外還有一些其他的五花八門的方法。
分析2:還不只這樣,聽起來似乎將來還會有更多的廠商類,而且每個類還會有各式各樣的方法。
分析3:遙控器應該知道如何解讀按鈕被按下的動作,然後發出正確的請求,但是遙控器不需知道這些家電自動化的細節。
分析4:我們不想讓遙控器包含一大堆if語句,例如“if slot1==Light,then light.On(),else if slot1==Hottub,then hottub.JetsOn()”.大家都知道這樣的設計很糟糕。且只要有新的廠商類進來,就必須修改現有的代碼,造成潛在的錯誤。
分析5:命令模式可以將“動作的請求者”從“動作的執行者”對象中解耦,在這個例子中,請求者可以是遙控器,而執行者對象就是廠商類其中之一的實例。
分析6:利用命令對象,把請求(例如打開電燈)封裝成一個特定對象(例如客廳電燈對象)。所以,如果對每個按鈕都存儲一個命令對象,那麼當按鈕被按下的時候,就可以請命令對象做相關的工作。遙控器並不需要知道工作內容是什麼,只要有個命令對象能喝正確的對象溝通,把事情做好就可以了。
分析7:使用這個模式,我們能夠創建一個API,將這些命令對象載入到按鈕插槽,讓遙控器的代碼儘量保持簡單。而把家電自動化的工作和進行該工作的對象一起封裝在命令對象中。
二、詳細說明
命令模式:將請求封裝成對象,這可以讓你使用不同的請求、隊列,或者日誌請求來參數化其他對象。命令模式也可以支持撤銷操作。
現在仔細看這個定義,我們知道一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。要達到這一點,命令對象將動作和接收者包進對象中。這個對象只暴露出一個execute()方法,當此方法被調用的時候,接收者就會進行這些動作。從外面來看,其他對象不知道究竟哪個接收者進行了哪些動作,只知道如果調用execute()方法,請求的目的就能達到。
1.定義命令模式類圖:
2.下麵的類圖提供了設計的全貌:
問:如果擁有了一個遙控器,卻無法光憑按下一個按鈕,就同時能弄暗燈光、打開音響和電視、設置好DVD,並讓熱水器開始加溫,那麼要這個遙控器還有什麼意義?
答:此時就可以用到命令模式的延伸“巨集命令”,製造一種新的命令,用來執行其他一堆命令。在巨集命令中,用命令數組(集合)存儲一大堆命令,當這個巨集命令被遙控器執行時,就一次性執行數組裡的每個命令。(具體內容,可在代碼列表查看)
問:接收者一定有必要存在嗎?為何命令對象不直接實現execute()方法的細節。
答:一般來說,我們儘量設計“傻瓜”命令對象,它只懂得調用一個接收者的一個行為。然而,有許多“聰明”命令對象會實現許多邏輯,直接完成一個請求。當然你可以設計聰明的命令對象,只是這樣一來,調用者和接收者之間的解耦程度是比不上“傻瓜”命令對象的,而且,你也不能夠把接收者當做參數傳給命令。
問:我可以創建PartyCommand,然後在它的execute()方法中調用其他的命令,利用這種做法實現Party模式(Party Mode)嗎?
答:你可以這麼做。然後,這等於把Party模式“硬編碼”到PartyCommand中。為什麼要這麼麻煩呢?利用巨集命令,你可以動態地決定PartyCommand是由哪些命令組成,所以巨集命令在使用上更靈活。一般來說,巨集命令的做法更優雅,也需要較少的新代碼。
問:我如何能夠實現多層次的撤銷操作?換句話說,我希望能夠按下撤銷按鈕許多次,撤銷到很早很早以前的狀態。
答:其實這相當容易做到,不要只記錄最後一個被執行的命令,而使用一個堆棧記錄操作過程的每一個命令。然後,不管什麼時候按下了撤銷按鈕,你都可以從堆棧中取出最上層的命令,然後調用它的Undo()方法。
3.命令模式的更多用途:
(1)隊列請求:命令可以將運算塊打包(一個接收者和一組動作),然後將它傳來傳去,就像是一般的對象一樣。它甚至可以在不同的線程中被調用。我們可以利用這樣的特性衍生一些應用,例如:日程安排、線程池、工作隊列等。
想象有一個工作隊列,你在某一端添加命令,然後另一端則是線程,線程進行下麵的動作,從隊列中取出一個命令,調用它的Execute()方法,等待這個調用完成,然後將此命令對象丟棄,再取出下一個命令......
(2)日誌請求:某些應用需要我們將所有的動作都記錄在日誌中,並能在系統死機之後,重新調用這些動作恢復到之前的狀態。通過新增兩個方法(Store()、Load()),命令模式能夠支持這一點。這種日誌的方式對於遙控器來說沒有意義,然而,有許多調用大型數據結構的動作的應用,無法在每次改變發生時被快速地存儲。通過使用記錄日誌,我們可以將上次檢查點之後的所有操作記錄下來,如果系統出現狀況,從檢查點開始應用這些操作。
比方說,對於電子錶格應用,我們可能想要實現的錯誤恢復方式是將電子錶格的操作記錄在日誌中,而不是每次電子錶格一有變化就記錄整個電子錶格。對更高級的應用而言,這些技巧可以被擴展應用到事務處理中,也就是說,一整組操作必須完成後才算有效。
4.總結:
(1)當需要將發出請求的對象和執行請求的對象解耦的時候,使用命令模式
(2)在被解耦的兩者之間是通過命令對象進行溝通的,命令對象封裝了接收者和一個或一組動作。
(3)調用者通過調用命令對象的execute()發出請求,這會使得接收者的動作被調用。
(4)調用者可以接受命令當做參數,甚至在運行時動態地進行。
(5)命令可以支持撤銷,做法是實現一個Undo()方法來回到execute()被執行前的狀態。
(6)巨集命令是命令的一種簡單的延伸,允許調用多個命令,巨集方法也可以支持撤銷。
(7)命令也可以用來實現日誌和事務系統。
三、代碼列表
/// <summary> /// 命令介面 /// </summary> public interface ICommand { /// <summary> /// 執行 /// </summary> void Execute(); /// <summary> /// 撤銷 /// </summary> void Undo(); } public class RemoteControl { private ICommand[] onCommands; private ICommand[] offCommands; private ICommand undoCommand; public RemoteControl() { onCommands = new ICommand[7]; offCommands = new ICommand[7]; ICommand noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand; } public void SetCommand(int slot, ICommand onCommand, ICommand offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void OnButtonWasPushed(int slot) { onCommands[slot].Execute(); undoCommand = onCommands[slot]; } public void OffButtonWasPushed(int slot) { offCommands[slot].Execute(); undoCommand = offCommands[slot]; } public void UndoButtonWasPushed() { undoCommand.Undo(); } public override string ToString() { StringBuilder sb=new StringBuilder(); sb.Append("\n---------Remote Control----------\n"); for (int i = 0; i < onCommands.Length; i++) { sb.AppendFormat("[slot {0}] {1} {2}",i,onCommands[i].GetType().FullName, offCommands[i].GetType().FullName); sb.AppendLine(); } return sb.ToString(); } } public class SimpleRemoteControl { private ICommand slot; public void SetCommand(ICommand command) { slot = command; } public void ButtonWasPressed() { slot.Execute(); } } //Worker /// <summary> /// 弔扇 /// </summary> public class CeilLingFan { public CeilLingFanSpeed speed { get; private set; } = 0; private string name; public CeilLingFan(string name) { this.name = name; } /// <summary> /// 高速 /// </summary> public void High() { speed = CeilLingFanSpeed.High; Console.WriteLine("{0} ceilLingFan is highs", name); } /// <summary> /// 中速 /// </summary> public void Medium() { speed = CeilLingFanSpeed.Medium; Console.WriteLine("{0} ceilLingFan is medium", name); } /// <summary> /// 低速 /// </summary> public void Low() { speed = CeilLingFanSpeed.Low; Console.WriteLine("{0} ceilLingFan is low", name); } /// <summary> /// 關閉 /// </summary> public void Off() { speed = CeilLingFanSpeed.Off; Console.WriteLine("{0} ceilLingFan is Off", name); } public enum CeilLingFanSpeed { High = 3, Medium = 2, Low = 1, Off = 0 } } /// <summary> /// 車庫門 /// </summary> public class GarageDoor { private string name; public GarageDoor(string name) { this.name = name; } /// <summary> /// 上升 /// </summary> public void Up() { Console.WriteLine("{0} Garage door is open", name); } /// <summary> /// 下降 /// </summary> public void Down() { Console.WriteLine("{0} Garage door is close", name); } /// <summary> /// 停止 /// </summary> public void Stop() { Console.WriteLine("{0} Garage door is stop", name); } /// <summary> /// 燈光開啟 /// </summary> public void LightOn() { Console.WriteLine("{0} Garage Light is on", name); } /// <summary> /// 燈光關閉 /// </summary> public void LightOff() { Console.WriteLine("{0} Garage Light is off", name); } } /// <summary> /// 熱浴盆 /// </summary> public class Hottub { /// <summary> /// 水迴圈 /// </summary> public void Circulata() { Console.WriteLine("Tub water is circulata"); } /// <summary> /// 噴射打開 /// </summary> public void JetsOn() { Console.WriteLine("Jets is on"); } /// <summary> /// 噴射關閉 /// </summary> public void JetsOff() { Console.WriteLine("Jets is Off"); } /// <summary> /// 設置溫度 /// </summary> public void SetTemperature() { Console.WriteLine("Default temperature is 46°C"); } } /// <summary> /// 燈光 /// </summary> public class Light { private string name; public Light(string name) { this.name = name; } /// <summary> /// 開啟 /// </summary> public void On() { Console.WriteLine("{0} light is On", name); } /// <summary> /// 關閉 /// </summary> public void Off() { Console.WriteLine("{0} light is Off", name); } } /// <summary> /// 立體聲 /// </summary> public class Stereo { private string name; public Stereo(string name) { this.name = name; } /// <summary> /// 開啟 /// </summary> public void On() { Console.WriteLine("{0} Stereo is on", name); } /// <summary> /// 關閉 /// </summary> public void Off() { Console.WriteLine("{0} Stereo is off", name); } /// <summary> /// 設置CD /// </summary> public void SetCd() { Console.WriteLine("Stereo Cd is on"); } /// <summary> /// 設置DVD /// </summary> public void SetDvd() { Console.WriteLine("Stereo Dvd is on"); } /// <summary> /// 設置收音機 /// </summary> public void SetRadio() { Console.WriteLine("Stereo radio is on"); } /// <summary> /// 設置音量 /// </summary> public void SetVolume(byte volume) { Console.WriteLine("Stereo volume is {0}", volume); } } /// <summary> /// 電視 /// </summary> public class TV { private string name; public TV(string name) { this.name = name; } /// <summary> /// 開啟 /// </summary> public void On() { Console.WriteLine("{0} TV is on", name); } /// <summary> /// 關閉 /// </summary> public void Off() { Console.WriteLine("{0} TV is off", name); } /// <summary> /// 設置頻道 /// </summary> public void SetInputChannel() { Console.WriteLine("Defualt channel is CCTV1"); } /// <summary> /// 設置音量 /// </summary> public void SetVolume() { Console.WriteLine("Defualt volume is 5"); } } //Commands /// <summary> /// 弔扇基礎命令類 /// </summary> public abstract class CeilingFanCommand : ICommand { protected CeilLingFan ceilLingFan; private CeilLingFan.CeilLingFanSpeed speed; public void Execute() { speed = ceilLingFan.speed; FanExecute(); } public abstract void FanExecute(); public void Undo() { switch (speed) { case CeilLingFan.CeilLingFanSpeed.High: ceilLingFan.High(); break; case CeilLingFan.CeilLingFanSpeed.Medium: ceilLingFan.Medium(); break; case CeilLingFan.CeilLingFanSpeed.Low: ceilLingFan.Low(); break; case CeilLingFan.CeilLingFanSpeed.Off: ceilLingFan.Off(); break; } } } public class CeilLingFanHighCommand : CeilingFanCommand { public CeilLingFanHighCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.High(); } } public class CeilLingFanLowCommand : CeilingFanCommand { public CeilLingFanLowCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.Low(); } } public class CeilLingFanMediumCommand : CeilingFanCommand { public CeilLingFanMediumCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.Medium(); } } public class CeilLingFanOffCommand : CeilingFanCommand { public CeilLingFanOffCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.Off(); } } public class GarageDoorCloseCommand : ICommand { private GarageDoor garageDoor; public GarageDoorCloseCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void Execute() { garageDoor.Down(); garageDoor.LightOff(); } public void Undo() { garageDoor.LightOn(); garageDoor.Up(); } } public class GarageDoorOpenCommand : ICommand { private GarageDoor garageDoor; public GarageDoorOpenCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void Execute() { garageDoor.Up(); garageDoor.LightOn(); } public void Undo() { garageDoor.Down(); garageDoor.LightOff(); } } public class HottubOffCommand:ICommand { private Hottub hottub; public HottubOffCommand(Hottub hottub) { this.hottub = hottub; } public void Execute() { hottub.JetsOff(); } public void Undo() { hottub.JetsOn(); } } public class HottubOnCommand : ICommand { private Hottub hottub; public HottubOnCommand(Hottub hottub) { this.hottub = hottub; } public void Execute() { hottub.JetsOn(); hottub.SetTemperature(); } public void Undo() { hottub.JetsOff(); } } public class LightOffCommand:ICommand { private Light light; public LightOffCommand(Light light) { this.light = light; } public void Execute() { light.Off(); } public void Undo() { light.On(); } } public class LightOnCommand : ICommand { private Light light; public LightOnCommand(Light light) { this.light = light; } public void Execute() { light.On(); } public void Undo() { light.Off(); } } /// <summary> /// 更多命令(巨集命令) /// </summary> public class MacroCommand:ICommand { private ICommand[] commands; public MacroCommand(ICommand[] commands) { this.commands = commands; } public void Execute() { foreach (ICommand command in commands) { command.Execute(); } } public void Undo() { foreach (ICommand command in commands) { command.Undo(); } } } /// <summary> /// 沒有命令 /// </summary> public class NoCommand : ICommand { public void Execute() { } public void Undo() { } } public class StereoOffCommand : ICommand { private Stereo stereo; public StereoOffCommand(Stereo stereo) { this.stereo = stereo; } public void Execute() { stereo.Off(); } public void Undo() { stereo.On(); } } public class StereoOnCommand : ICommand { private Stereo stereo; public StereoOnCommand(Stereo stereo) { this.stereo = stereo; } public void Execute() { stereo.On(); } public void Undo() { stereo.Off(); } } public class StereoOnWithCDCommand:ICommand { private Stereo stereo; public StereoOnWithCDCommand(Stereo stereo) { this.stereo = stereo; } public void Execute() { stereo.On(); stereo.SetCd(); stereo.SetVolume(11); } public void Undo() { stereo.Off(); } } public class TVOffCommand:ICommand { private TV tv; public TVOffCommand(TV tv) { this.tv = tv; } public void Execute() { tv.Off(); } public void Undo() { tv.On(); } } public class TVOnCommand : ICommand { private TV tv; public TVOnCommand(TV tv) { this.tv = tv; } public void Execute() { tv.On(); tv.SetInputChannel(); tv.SetVolume(); } public void Undo() { tv.Off(); } } //RunTest [Test] public void RemoteLoader() { //單個插槽的簡單控制裝置 /*SimpleRemoteControl remote = new SimpleRemoteControl(); Light light = new Light(); GarageDoor garageDoor = new GarageDoor(); LightOnCommand lightOn = new LightOnCommand(light); GarageDoorOpenCommand garageOpen = new GarageDoorOpenCommand(garageDoor); remote.SetCommand(lightOn); remote.ButtonWasPressed(); remote.SetCommand(garageOpen); remote.ButtonWasPressed();*/ //多個插槽(含有關閉功能)的控制裝置 /*RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); Light kitchenLight = new Light("Kitchen"); CeilLingFan ceilLingFan = new CeilLingFan("Living Room"); GarageDoor garageDoor = new GarageDoor(""); Stereo stereo = new Stereo("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight); LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight); CeilLingFanLowCommand ceilingFanLow = new CeilLingFanLowCommand(ceilLingFan); CeilLingFanOffCommand ceilLingFanOff = new CeilLingFanOffCommand(ceilLingFan); GarageDoorOpenCommand garageDoorOpen = new GarageDoorOpenCommand(garageDoor); GarageDoorCloseCommand garageDoorClose = new GarageDoorCloseCommand(garageDoor); StereoOnWithCDCommand stereoOnWithCd = new StereoOnWithCDCommand(stereo); StereoOffCommand stereoOff = new StereoOffCommand(stereo); remoteControl.SetCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.SetCommand(1, kitchenLightOn, kitchenLightOff); remoteControl.SetCommand(2, ceilingFanLow, ceilLingFanOff); remoteControl.SetCommand(3, garageDoorOpen, garageDoorClose); remoteControl.SetCommand(4, stereoOnWithCd, stereoOff); Console.WriteLine(remoteControl); remoteControl.OnButtonWasPushed(0); remoteControl.OffButtonWasPushed(0); remoteControl.OnButtonWasPushed(1); remoteControl.OffButtonWasPushed(1); remoteControl.OnButtonWasPushed(2); remoteControl.OffButtonWasPushed(2); remoteControl.OnButtonWasPushed(3); remoteControl.OffButtonWasPushed(3); remoteControl.OnButtonWasPushed(4); remoteControl.OffButtonWasPushed(4);*/ //多個插槽(含有關閉功能,含有撤銷功能)的控制裝置 /*RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); remoteControl.SetCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.OnButtonWasPushed(0); remoteControl.OffButtonWasPushed(0); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed(); remoteControl.OffButtonWasPushed(0); remoteControl.OnButtonWasPushed(0); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed();*/ //多個插槽(含有狀態對象的撤銷功能)的控制裝置 /*RemoteControl remoteControl = new RemoteControl(); CeilLingFan ceilLingFan = new CeilLingFan("Living Room"); CeilLingFanHighCommand ceilingFanHigh = new CeilLingFanHighCommand(ceilLingFan); CeilLingFanMediumCommand ceilLingFanMedium = new CeilLingFanMediumCommand(ceilLingFan); CeilLingFanOffCommand ceilLingFanOff = new CeilLingFanOffCommand(ceilLingFan); remoteControl.SetCommand(0, ceilLingFanMedium, ceilLingFanOff); remoteControl.SetCommand(1, ceilingFanHigh, ceilLingFanOff); remoteControl.OnButtonWasPushed(0); remoteControl.OffButtonWasPushed(0); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed(); remoteControl.OnButtonWasPushed(1); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed();*/ //多個插槽(含有巨集命令)的控制裝置 RemoteControl remoteControl = new RemoteControl(); Light light = new Light("Living Room"); TV tv = new TV("Living Room"); Stereo stereo = new Stereo("Living Room"); Hottub hottub = new Hottub(); LightOnCommand lightOn = new LightOnCommand(light); StereoOnCommand stereoOn = new StereoOnCommand(stereo); TVOnCommand tvOn = new TVOnCommand(tv); HottubOnCommand hottubOn = new HottubOnCommand(hottub); LightOffCommand lightOff = new LightOffCommand(light); StereoOffCommand stereoOff = new StereoOffCommand(stereo); TVOffCommand tvOff = new TVOffCommand(tv); HottubOffCommand hottubOff = new HottubOffCommand(hottub); ICommand[] partyOn = { lightOn, stereoOn, tvOn, hottubOn }; ICommand[] partyOff = { lightOff, stereoOff, tvOff, hottubOff }; MacroCommand partyOnMacro = new MacroCommand(partyOn); MacroCommand partyOffMacro = new MacroCommand(partyOff); remoteControl.SetCommand(0, partyOnMacro, partyOffMacro); Console.WriteLine(remoteControl); Console.WriteLine("-----Pushing Macro On-----"); remoteControl.OnButtonWasPushed(0); Console.WriteLine("=====Pushing Macro Off----"); remoteControl.OffButtonWasPushed(0); }View Code
---------------------------------以上內容根據《head frist design mode》進行整理