iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
自我挑戰組

深入淺出設計模式 (Head First Design Pattern) - 重點整理及範例分享系列 第 23

[深入淺出設計模式] Ch6 The Command Pattern (4) - 【Meta Command Pattern】範例實作

  • 分享至 

  • xImage
  •  

Meta Command Pattern

"Allows you to create macros of commands so that you can execute multiple commands at once."

我們可以利用 Meta Command 一次執行很多個命令,延續上篇的範例中,還沒完成的 undo() 功能就是應用這個概念,undo就是 ctrl+z 回到上一步,假如現在執行 LightOffCommand()關燈,那undo後就會回到LightOnCommand()開燈的狀態。


實作未設定功能的 NoCommand() 以及 Undo 按鈕的 undoCommand()

前篇程式碼少了一個判斷,如果按下沒設定功能的按鈕會直接報錯XD
所以我們新增一個NoCommand()類別,在Remote()建構子先把所有按鈕都初始化成 NoCommand(),另外要把
undoCommand 也先初始化成沒有命令。
一開始可能會直觀地覺得,undo不就是取消現在的動作,以中文理解就是什麼都不做?
為什麼要先記住現在的命令?

其實Undo翻成中文應該要理解成回到上一步,假設現在把燈打開,我們取消動作後,若先前沒有undoCommand這個物件去記憶動作,燈就會保持在打開的狀態,下一次我們再按一下就會把燈關掉,而不是我們想的把燈打開。(完全像是在繞口令)

所以要修改過後的程式碼會變成以下:

首先新增一個類別 NoCommand()

public interface Command{
    public void execute();
    public void undo();
}

public class NoCommand implements Command{
    public void execute(){
        System.out.println("The button hasn't been assign to any command.");
    }
    public void undo(){}
}

修改遙控器類別的初始化、新增Undo按鈕

public class Remote extends Invoker{
    ArrayList<Command> onCommands;
    ArrayList<Command> offCommands;
    Command undoCommand;
    
    public Remote(){
        NoCommand noCommand = new NoCommand();
        
        //先把八個按鈕包含undo都初始化成 noCommand
        onCommands = new ArrayList<Command>(Collections.nCopies(8, noCommand));
        offCommands = new ArrayList<Command>(Collections.nCopies(8, noCommand));
        undoCommand = new NoCommand();
    }

    public void setCommand(int btnVal, Command onCommand, Command offCommand){
        onCommands.set(btnVal - 1, onCommand);
        offCommands.set(btnVal - 1, offCommand);  
    }

    public void pressOnBtn(int btnVal){     
        System.out.print(this.toString(btnVal, "ON"));   
        onCommands.get(btnVal - 1).execute();   
        
        //要先讓undo按鈕知道現在執行的命令,如果使用者按下undo,就是執行跟undo記憶的相反的命令
        //若等下使用者按下undo,才會讓命令回到Off
        undoCommand = onCommands.get(btnVal - 1);
    }

    public void pressOffBtn(int btnVal){        
        System.out.print(this.toString(btnVal, "OFF"));  
        offCommands.get(btnVal - 1).execute();    
        //若等下使用者按下undo,才會讓命令回到On
        undoCommand = offCommands.get(btnVal - 1);
    }

    public void pressUndoBtn(){
        System.out.print(this.toString(0, "UNDO"));  
        onCommands.get(7).execute();   
        undoCommand.undo();
    }
    
    // 可以顯示目前所有按鈕的功能
    @Override
    public String toString() {
        StringBuffer stringBuf = new StringBuffer();
        stringBuf.append("\n_________________   Remote Control   _________________\n\n");
        for(int i = 0; i < onCommands.size() - 1; i++){
            stringBuf.append("[ SLOT "+ (i + 1) + "] " + onCommands.get(i).getClass().getName());
            stringBuf.append("    " + "[ SLOT " + (i + 1) + "] " + offCommands.get(i).getClass().getName() + "\n");
        }
        stringBuf.append("[ SLOT " + (onCommands.size()) + "] " + undoCommand.getClass().getName() + " -> " + "UNDO \n");
        return stringBuf.toString();
    }
    
    //可以顯示目前按下的按鈕
    public String toString(int btnVal, String func){
        return "Press " + func + " button " + btnVal + " -> ";
    }
}

以開關燈的命令為例,undo() 就是執行相反的令令

public class LightOnCommand implements Command{
    Light light;

    public LightOnCommand(Light light){
        this.light = light;
    }

    public void execute(){        
        light.on();
    }

    public void undo(){
        light.off();
    }
}

public class LightOffCommand implements Command{
    Light light;

    public LightOffCommand(Light light){
        this.light = light;
    }

    public void execute(){        
        light.off();
    }

    public void undo(){
        light.on();
    }
}

最後再測試看看:

public class CommandPattern {
    public static void main(String[] args) {
        Remote remote = new Remote();
        Light livingroomLight = new Light("living room");
        Light studioLight = new Light("Music Studio");

        Curtain bedroomCurtain = new Curtain("bedroom");
        Curtain livingroomCurtain = new Curtain("Livingroom");
        Fridge kitchenFridge = new Fridge("kitchen");

        CurtainOpenCommand livingroomCurtainOpen = new CurtainOpenCommand(livingroomCurtain);
        CurtainCloseCommand livingroomCurtainClose = new CurtainCloseCommand(livingroomCurtain);

        CurtainOpenCommand bedroomCurtainOpen = new CurtainOpenCommand(bedroomCurtain);
        CurtainCloseCommand bedroomCurtainClose = new CurtainCloseCommand(bedroomCurtain);

        LightOnCommand livingroomLightOnCommand = new LightOnCommand(livingroomLight);
        LightOffCommand livingroomLightOffCommand = new LightOffCommand(livingroomLight);

        LightOnCommand studioLightOnCommand = new LightOnCommand(studioLight);
        LightOffCommand studioLightOffCommand = new LightOffCommand(studioLight);

        FridgeOpenCommand kitchenFridgeOpenCommand = new FridgeOpenCommand(kitchenFridge);
        FridgeCloseCommand kitchenFridgeOffCommand = new FridgeCloseCommand(kitchenFridge);

        System.out.println(remote);

        remote.setCommand(1, livingroomCurtainOpen, livingroomCurtainClose);
        remote.setCommand(2, livingroomLightOnCommand, livingroomLightOffCommand);
        remote.setCommand(3, kitchenFridgeOpenCommand, kitchenFridgeOffCommand);
        remote.setCommand(4, bedroomCurtainOpen, bedroomCurtainClose);
        remote.setCommand(5, studioLightOnCommand, studioLightOffCommand);

        System.out.println(remote);

        remote.pressOffBtn(4);
        remote.pressOnBtn(5);       
        remote.pressUndoBtn();
        remote.pressOnBtn(5);
        remote.pressOnBtn(1);
        remote.pressOffBtn(7);
    }
}

輸出結果
https://ithelp.ithome.com.tw/upload/images/20231008/201631785bgWxHC7Eo.png


參考資料:

  1. 《深入淺出設計模式 (Head First Design Patterns) 》
  2. 書中官方程式碼傳送門
  3. 本篇完整程式碼傳送門

Disclaimer
因為讀的是原文版,所以難免會有翻譯詞不達意或是專有名詞上的差異,有錯誤的話歡迎在留言區一起交流!


上一篇
[深入淺出設計模式] Ch6 The Command Pattern (3) - 【命令模式】範例實作 範例 Home Automation Remote Control
下一篇
[深入淺出設計模式] Ch7 The Adapter Pattern (1) - 【轉接器模式】範例 SimUDuck 鴨子模擬器
系列文
深入淺出設計模式 (Head First Design Pattern) - 重點整理及範例分享35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言