命令模式 之 管理智慧家電
簡化版:
定義:將“請求”封裝成物件,以便使用不同的請求、佇列或者日誌來引數化其他物件。命令模式也支援可撤銷的操作。(簡化: 將請求封裝成物件,將動作請求者和動作執行者解耦。)
- 需求:最近智慧家電很火熱,假設現在有電視、電腦、電燈等家電,現在需要你做個遙控器控制所有家電的開關,要求做到每個按鈕對應的功能供使用者個性化,對於新買入家電要有非常強的擴充套件性。
- 1、家電的API:Door.java
- 2、把命令封裝成類:
- 統一的命令介面:Command.java
- 家電實現該介面:DoorOpenCommand.java
- 3、遙控器:ControlPanel.java
- 4、定義一個命令,可以幹一系列的事情:QuickCommand.java
- 5、遙控器面板執行:CommandActivity.java
QuickCommand quickCloseCommand = new QuickCommand(new Command[]{new LightOffCommand(light), new ComputerOffCommand(computer), new DoorCloseCommand(door)}); controlPanel.setCommands(6, quickOpenCommand); controlPanel.keyPressed(6);
controlPanel.setCommands(0, new DoorOpenCommand(door));// 開門 controlPanel.keyPressed(0);
詳細解釋:
定義:將“請求”封裝成物件,以便使用不同的請求、佇列或者日誌來引數化其他物件。命令模式也支援可撤銷的操作。
這尼瑪定義,看得人蛋疼,看不明白要淡定,我稍微簡化一下:將請求封裝成物件,將動作請求者和動作執行者解耦。好了,直接用例子來說明。
需求:最近智慧家電很火熱啊,未來尼瑪估計冰箱都會用支付寶自動買東西了,,,,假設現在有電視、電腦、電燈等家電,現在需要你做個遙控器控制所有家電的開關,要求做到每個按鈕對應的功能供使用者個性化,對於新買入家電要有非常強的擴充套件性。
這個需求一看,尼瑪要是沒有什麼個性化、擴充套件性還好說啊,直接針對每個遙控器的按鈕onClick,然後在裡面把程式碼寫死就搞定了,但是個性化怎麼整,還要有擴充套件性。。。
好了,下面命令模式出場,命令模式的核心就是把命令封裝成類,對於命令執行者不需要知道現在執行的具體是什麼命令。
package com.zhy.pattern.command; /** * 門 * @author zhy * */ public class Door { public void open() { System.out.println("開啟門"); } public void close() { System.out.println("關閉門"); } } package com.zhy.pattern.command; /** * 電燈 * @author zhy * */ public class Light { public void on() { System.out.println("開啟電燈"); } public void off() { System.out.println("關閉電燈"); } } package com.zhy.pattern.command; /** * 電腦 * @author zhy * */ public class Computer { public void on() { System.out.println("開啟電腦"); } public void off() { System.out.println("關閉電腦"); } }
看來我們有電燈、電腦、和門,並且開關的介面的設計好了。接下來看如何把命令封裝成類:
package com.zhy.pattern.command; public interface Command { public void execute(); }
package com.zhy.pattern.command; /** * 關閉電燈的命令 * @author zhy * */ public class LightOffCommond implements Command { private Light light ; public LightOffCommond(Light light) { this.light = light; } @Override public void execute() { light.off(); } }
package com.zhy.pattern.command; /** * 開啟電燈的命令 * @author zhy * */ public class LightOnCommond implements Command { private Light light ; public LightOnCommond(Light light) { this.light = light; } @Override public void execute() { light.on(); } }
好了,不貼那麼多了,既然有很多命令,按照設計原則,我們肯定有個超型別的Command,然後各個子類,看我們把每個命令(請求)都封裝成類了。接下來看我們的遙控器。
package com.zhy.pattern.command; /** * 控制器面板,一共有9個按鈕 * * @author zhy * */ public class ControlPanel { private static final int CONTROL_SIZE = 9; private Command[] commands; public ControlPanel() { commands = new Command[CONTROL_SIZE]; /** * 初始化所有按鈕指向空物件 */ for (int i = 0; i < CONTROL_SIZE; i++) { commands[i] = new NoCommand(); } } /** * 設定每個按鈕對應的命令 * @param index * @param command */ public void setCommand(int index, Command command) { commands[index] = command; } /** * 模擬點選按鈕 * @param index */ public void keyPressed(int index) { commands[index].execute(); } }
package com.zhy.pattern.command; /** * @author zhy * */ public class NoCommand implements Command { @Override public void execute() { } }
注意看到我們的遙控器有9個按鈕,提供了設定每個按鈕的功能和點選的方法,還有注意到我們使用了一個NoCommand物件,叫做空物件,這個物件的好處就是,我們不用執行前都判斷個if(!=null),並且提供了一致的操作。
最後測試一下程式碼:
package com.zhy.pattern.command; public class Test { public static void main(String[] args) { /** * 三個家電 */ Light light = new Light(); Door door = new Door(); Computer computer = new Computer(); /** * 一個控制器,假設是我們的app主介面 */ ControlPanel controlPanel = new ControlPanel(); // 為每個按鈕設定功能 controlPanel.setCommand(0, new LightOnCommond(light)); controlPanel.setCommand(1, new LightOffCommond(light)); controlPanel.setCommand(2, new ComputerOnCommond(computer)); controlPanel.setCommand(3, new ComputerOffCommond(computer)); controlPanel.setCommand(4, new DoorOnCommond(door)); controlPanel.setCommand(5, new DoorOffCommond(door)); // 模擬點選 controlPanel.keyPressed(0); controlPanel.keyPressed(2); controlPanel.keyPressed(3); controlPanel.keyPressed(4); controlPanel.keyPressed(5); controlPanel.keyPressed(8);// 這個沒有指定,但是不會出任何問題,我們的NoCommand的功勞 } }
輸出結果:
可以看到任意按鈕可以隨意配置任何命令,再也不需要尼瑪的變一下需求改程式碼了,隨便使用者怎麼個性化了。其實想白了,這裡的設定我們還可以配置到一個配置檔案中,完全的解耦有木有。
好了,使用者對於這個按鈕可能還不是太滿意,使用者希望夜深人靜的時候,能夠提供個按鈕直接關門、關燈、開電腦,,,,大家懂的,,,我們稍微修改下程式碼,滿足他
定義一個命令,使用者幹一些列的事,可配置,且與原來的命令保持介面一致:
package com.zhy.pattern.command; /** * 定義一個命令,可以幹一系列的事情 * * @author zhy * */ public class QuickCommand implements Command { private Command[] commands; public QuickCommand(Command[] commands) { this.commands = commands; } @Override public void execute() { for (int i = 0; i < commands.length; i++) { commands[i].execute(); } } }
// 定義一鍵搞定模式 QuickCommand quickCommand = new QuickCommand(new Command[] { new DoorOffCommond(door), new LightOffCommond(light), new ComputerOnCommond(computer) }); System.out.println("****點選一鍵搞定按鈕****"); controlPanel.setCommand(8, quickCommand); controlPanel.keyPressed(8);
是不是很完美。
最後,繼續來談談命令模式,命令模式就是把命令封裝成物件,然後將動作請求者與動作執行者完全解耦,上例中遙控器的按鈕和電器一毛錢關係都沒吧。
還記得定義中提到了佇列,命令模式如何用於佇列呢,比如飯店有很多個點菜的地方,有一個做菜的地方,把點菜看作命令,做菜看作命令執行者,不斷有人點菜就相當於把菜加入佇列,對於做菜的只管從佇列裡面取,取一個做一個。
定義中還提到了日誌,日誌一般用於記錄使用者行為,或者在異常時恢復時用的,比如每個命令現在包含兩個方法,一個執行execute,一個undo(上例中為了方便大家理解,沒有寫undo),我們可以把使用者所有命令呼叫儲存到日誌中,比如使用者操作不當了,電器異常了,只需要把日誌中所有的命令拿出來執行一遍undo就完全恢復了,是吧,就是這麼個意思。