繼續設計模式哈,今天帶來命令模式,二話不說,先看定義:
定義:將“請求”封裝成對象,以便使用不同的請求、隊列或者日誌來參數化其他對象。命令模式也支持可撤銷的操作。
這尼瑪定義,看得人蛋疼,看不明白要淡定,我稍微簡化一下:將請求封裝成對象,將動作請求者和動作執行者解耦。好了,直接用例子來說明。
需求:最近智能家電很火熱啊,未來尼瑪估計冰箱都會用支付寶自動買東西了,,,,假設現在有電視、電腦、電燈等家電,現在需要你做個遙控器控制所有家電的開關,要求做到每個按鈕對應的功能供用戶個性化,對於新買入家電要有非常強的擴展性。
這個需求一看,尼瑪要是沒有什麼個性化、擴展性還好說啊,直接針對每個遙控器的按鈕onClick,然後在裏面把代碼寫死就搞定了,但是個性化怎麼整,還要有擴展性。。。
好了,下面命令模式出場,命令模式的核心就是把命令封裝成類,對於命令執行者不需要知道現在執行的具體是什麼命令。
1、首先看下我們擁有的家電的API:
- 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();
- }
- }
- package com.zhy.pattern.command;
- /**
- * 開電腦的命令
- * @author zhy
- *
- */
- public class ComputerOnCommond implements Command
- {
- private Computer computer ;
- public ComputerOnCommond( Computer computer)
- {
- this.computer = computer;
- }
- @Override
- public void execute()
- {
- computer.on();
- }
- }
- package com.zhy.pattern.command;
- /**
- * 關電腦的命令
- * @author zhy
- *
- */
- public class ComputerOffCommond implements Command
- {
- private Computer computer ;
- public ComputerOffCommond( Computer computer)
- {
- this.computer = computer;
- }
- @Override
- public void execute()
- {
- computer.off();
- }
- }
好了,不貼那麼多了,既然有很多命令,按照設計原則,我們肯定有個超類型的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就完全恢復了,是吧,就是這麼個意思。
好了,各位留個言、點個贊算是對我的支持,多謝大家~