一、引言 先看需求:設計一個家電遙控器系統,每個家電由開、關兩個按鈕控制, 每個家電都由各自的廠商提供了實現方法,我們只需要調用這些方法即可,如圖所示: 如何實現這個功能呢? 第一步我們要排除的實現方式就是if條件判斷,因為一旦增加家電,我們就必須修改代碼,這不符合我們的設計思路。 然後我們想想,遙 ...
一、引言
先看需求:設計一個家電遙控器系統,每個家電由開、關兩個按鈕控制, 每個家電都由各自的廠商提供了實現方法,我們只需要調用這些方法即可,如圖所示:
如何實現這個功能呢?
第一步我們要排除的實現方式就是if條件判斷,因為一旦增加家電,我們就必須修改代碼,這不符合我們的設計思路。
然後我們想想,遙控按鈕只是發出一個請求,具體的實現是通過各自廠商的API,我們應該讓遙控器(動作的請求者)從廠商的API(動作的執行者)中解耦出來。可是怎麼去解耦呢?畢竟按鈕動作和家電行為是息息相關的。
這個時候就可以使用命令模式,來認識一下命令模式。
二、命令模式
先看定義:命令模式是把一個操作或者行為抽象為一個對象中,通過對命令的抽象化來使得發出命令的責任和執行命令的責任分隔開。命令模式的實現可以提供命令的撤銷和恢復功能
聽著還是很抽象,我們再舉一個例子,《破產姐妹》中熟悉的場景 ,顧客到餐廳點單,服務員MAX拿了訂單,放在訂單櫃臺,廚師Oleg根據訂單準備餐點。
分析下其中的過程,把訂單想象成一個用來請求準備餐點的對象,訂單對象可以被傳遞,服務員MAX負責傳遞對象,訂單的介面中只包含一個orderUp()方法,這個方法封裝了而準備餐點所需的動作,訂單內有一個“需要進行準備工作的對象”(也就是廚師Oleg)的引用,這一切都封裝起來,MAX甚至不需要知道訂單上有什麼,她只負責傳遞。
有沒有清楚一些,轉成類圖:
類圖中可以看出,其中的幾個角色:
- 客戶角色(Client):發出一個具體的命令並設置其接受者。
- 調用者(Invoker):持有一個命令對象,負責命令對象執行命令。
- 命令角色(Command):聲明命令介面,並具備一個讓接收者執行的方法。
- 具體命令角色(ConreteCommand):定義了動作和接收者之間的綁定關係,負責調用接收者的方法。
- 接受者(Receiver):負責具體動作的執行。
三、代碼實現
命令介面:
//命令介面 public interface Command { //命令方法 void execute(); }
命令接收者
//命令接收者(Receiver),電燈 public class Light { private String name; public Light(String name){ this.name=name; } //開燈操作 public void on(){ System.out.println(name+":開燈!"); } //關燈操作 public void off(){ System.out.println(name+":關燈"); } }
綁定命令與接收者關係
//綁定命令與接收者關係ConreteCommand public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light){ this.light=light; } //具體命令方法 public void execute() { light.on(); } }
調用者(Invoker)
//命令模式的客戶(Invoker) public class SimpleRemoteControl { //命令介面 Command solt; public void setCommand(Command command){ this.solt=command; } //命令方法 public void buttonWasPressed(){ solt.execute(); } }
運行:
private static void simpleControl() { //遙控器調用者 SimpleRemoteControl control=new SimpleRemoteControl(); //電燈 Light light=new Light("客廳"); //具體命令類 LightOnCommand lightOnCommand=new LightOnCommand(light); //設置命令 control.setCommand(lightOnCommand); //命令方法 control.buttonWasPressed(); }
結果:
這裡其實只實現了其中一個按鈕,讓我們來補充一些代碼
增加多一個接收者:
//另外一個接受者吊燈 public class CeilingFan { private String name; public CeilingFan(String name){ this.name=name; } public void on(){ System.out.println(name+":打開"); } public void off(){ System.out.println(name+":關閉"); } }
//吊燈的開燈命令 public class CeilingFanOffCommand implements Command { CeilingFan ceilingFan; public CeilingFanOffCommand(CeilingFan ceilingFan){ this.ceilingFan=ceilingFan; } //具體命令方法 public void execute() { ceilingFan.off(); } } //吊燈的關燈命令 public class CeilingFanOnCommand implements Command { CeilingFan ceilingFan; public CeilingFanOnCommand(CeilingFan ceilingFan){ this.ceilingFan = ceilingFan; } //具體命令方法 public void execute() { ceilingFan.on(); } }
調用者遙控:
//遙控調用 public class RemoteControl { //申明命令數組 Command[] onCommands; Command[] offCommands; public RemoteControl(){ onCommands=new Command[4]; offCommands=new Command[4]; } //設置命令 public void setCommand(int solt,Command onCommand,Command offCommand){ onCommands[solt]=onCommand; offCommands[solt]=offCommand; } //打開按鈕 public void onButtonWasPressed(int solt){ onCommands[solt].execute(); } //關閉按鈕 public void offButtonWasPressed(int solt){ offCommands[solt].execute(); } }
實際操作遙控:
private static void control() { RemoteControl remoteControl=new RemoteControl(); Light roomLight=new Light("客廳燈"); Light kitchLight=new Light("廚房燈"); CeilingFan roomCeilingFan=new CeilingFan("客廳弔扇"); CeilingFan kitchCeilingFan=new CeilingFan("廚房弔扇"); //電燈相關命令 LightOnCommand roomLightOnCommand=new LightOnCommand(roomLight); LightOnCommand kitchLightOnCommand=new LightOnCommand(kitchLight); LightOffCommand roomLightOffCommand=new LightOffCommand(roomLight); LightOffCommand kitchLightOffCommand=new LightOffCommand(kitchLight); //弔扇相關命令 CeilingFanOnCommand roomCeilingFanOnCommand =new CeilingFanOnCommand(roomCeilingFan); CeilingFanOnCommand kitchCeilingFanOnCommand =new CeilingFanOnCommand(kitchCeilingFan); CeilingFanOffCommand roomCeilingFanOffCommand =new CeilingFanOffCommand(roomCeilingFan); CeilingFanOffCommand kitchCeilingFanOffCommand =new CeilingFanOffCommand(kitchCeilingFan); //將命令載入到卡槽中 remoteControl.setCommand(0,roomLightOnCommand,roomLightOffCommand); remoteControl.setCommand(1,kitchLightOnCommand,kitchLightOffCommand); remoteControl.setCommand(2,roomCeilingFanOnCommand,roomCeilingFanOffCommand); remoteControl.setCommand(3,kitchCeilingFanOnCommand,kitchCeilingFanOffCommand); //使用遙控 remoteControl.onButtonWasPressed(0); remoteControl.offButtonWasPressed(0); remoteControl.onButtonWasPressed(1); remoteControl.offButtonWasPressed(1); remoteControl.onButtonWasPressed(2); remoteControl.offButtonWasPressed(2); remoteControl.onButtonWasPressed(3); remoteControl.offButtonWasPressed(3); }
運行結果:
四、總結
在下麵的情況下可以考慮使用命令模式:
- 系統需要支持命令的撤銷(undo)。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo方法吧命令所產生的效果撤銷掉。命令對象還可以提供redo方法,以供客戶端在需要時,再重新實現命令效果。
- 系統需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命周期。意思為:原來請求的發出者可能已經不存在了,而命令對象本身可能仍是活動的。這時命令的接受者可以在本地,也可以在網路的另一個地址。命令對象可以串列地傳送到接受者上去。
- 如果一個系統要將系統中所有的數據消息更新到日誌里,以便在系統崩潰時,可以根據日誌里讀回所有數據的更新命令,重新調用方法來一條一條地執行這些命令,從而恢復系統在崩潰前所做的數據更新。
- 系統需要使用命令模式作為“CallBack(回調)”在面向對象系統中的替代。Callback即是先將一個方法註冊上,然後再以後調用該方法。
優點: 1、降低了系統耦合度。 2、新的命令可以很容易添加到系統中去。
缺點:使用命令模式可能會導致某些系統有過多的具體命令類。
另附源碼地址:https://gitee.com/yuanqinnan/pattern