1. 程式人生 > 實用技巧 >命令模式 之 管理智慧家電

命令模式 之 管理智慧家電

簡化版:

定義:將“請求”封裝成物件,以便使用不同的請求、佇列或者日誌來引數化其他物件。命令模式也支援可撤銷的操作。(簡化: 將請求封裝成物件,將動作請求者和動作執行者解耦。)

  • 需求:最近智慧家電很火熱,假設現在有電視、電腦、電燈等家電,現在需要你做個遙控器控制所有家電的開關,要求做到每個按鈕對應的功能供使用者個性化,對於新買入家電要有非常強的擴充套件性。
  • 1、家電的API:Door.java
  • 2、把命令封裝成類:
  • 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就完全恢復了,是吧,就是這麼個意思。