2019年3月16日 星期六

設計模式(6) 命令模式 (Command Pattern)

此篇介紹GoF設計模式(Design Pattern)的其中一種—命令模式 (Command Pattern)。


適用問題:

  命令模式最好的體現就是遙控器了,遙控器上有許多按鍵,透過按鍵我們可以要求某物件執行命令,例如我們按下有開燈功能的按鍵,燈泡就會亮這樣。
  命令模式適用的情況通常存在請求者與執行者兩個角色,以餐廳為例,客人與廚師分別就代表請求者與執行者,而餐廳裡的服務生就好比是遙控器,客人向服務生點餐,服務生再傳遞點餐內容給廚師,客人須透過服務生這個角色間接傳遞指令給廚師,這樣的安排是比起客人直接向廚師點餐更加彈性,可以作到像是服務生等收集多一點的點菜單後才一起告知廚房。
  命令模式將請求封裝成命令物件,方便我們做到照順序執行之類的需求,管理上有許多好處。



命令模式定義:
  "The Command Pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations." (將一個請求封裝成一個物件,讓你可用不同的請求對客戶進行參數化、對請求排隊或記錄請求日誌,以及支援可取消的操作)


命令模式UML:
    Receiver:Receiver知道如何完成工作處理請求。(電燈泡 / 廚師)
    Invoker:持有命令,可呼叫命令的execute()。(遙控器 / 服務生)



命令模式code(以燈泡為例)(C++):
class Command{
public:
    virtual void execute() = 0;
};

class Light{
public:
    void on(){ cout<<"The light is on\n"; }
    void off(){ cout<<"The light is off\n"; }
};


class LightOnCommand : public Command{
private:
    Light *mLight;
public:
    LightOnCommand(Light *light):mLight(light){}
    void execute(){
        mLight->on();
    }
}

class LightOffCommand : public Command{
private:
    Light *mLight;
public:
    LightOffCommand(Light *light):mLight(light){}
    void execute(){
        mLight->off();
    }
}

class RemoteControl{
private:
    Command *mCmd;
public:
    void setCommand(Command *cmd){
        mCmd = cmd;
    }
    void buttonPressed(){
        mCmd->execute();
    }
}
int main() {
    Light *light = new Light;

    LightOnCommand *lightOn = new LightOnCommand(light);
    LightOffCommand *lightOff = new LightOffCommand(light);

    RemoteControl *control = new RemoteControl;

    control->setCommand(lightOn);
    control->buttonPressed();
    control->setCommand(lightOff);
    control->buttonPressed();

    delete light, lightOn, lightOff, control;

    return 0;
}

  上面這個例子是遙控器上只有一個按鍵,因此要一直重setCommand,若想要多按鍵,可以修改 class RemoteControl的mCmd成 vector之類能裝複數東西的容器,其他函式則要做點調整增加能指定是要下第幾個command用的參數。

  Command的初始化建議初始化成下面這個什麼也不會執行的命令。
class NoCommand : public Command{
public:
    void execute(){}
}


命令模式的一些應用:
  1. queuing requests:設計一佇列內依序排許多request commands,空閒的thread會從佇列的前頭提取一個命令,並執行其execute()函式。

  2. logging request:為了因應當機中斷執行,我們需要log存下action()動作以利恢復,作法是在命令物件加入save()與store()函式。 (但這個問題好像現在很少這麼解決,而是改建立一個介面Logger內有 void writeFile(String pathName, Object object) 與 Object readFile(String pathName) 兩個函式)




  參考資料:
    1. Head First Design Patterns
    2. code來源:https://www.bogotobogo.com/DesignPatterns/command.php


沒有留言:

張貼留言