1. 程式人生 > >設計模式第六篇-命令模式

設計模式第六篇-命令模式

一、引言

先看需求:設計一個家電遙控器系統,每個家電由開、關兩個按鈕控制, 每個家電都由各自的廠商提供了實現方法,我們只需要呼叫這些方法即可,如圖所示:

如何實現這個功能呢?

第一步我們要排除的實現方式就是if條件判斷,因為一旦增加家電,我們就必須修改程式碼,這不符合我們的設計思路。

然後我們想想,遙控按鈕只是發出一個請求,具體的實現是通過各自廠商的API,我們應該讓遙控器(動作的請求者)從廠商的API(動作的執行者)中解耦出來。可是怎麼去解耦呢?畢竟按鈕動作和家電行為是息息相關的。

這個時候就可以使用命令模式,來認識一下命令模式。

二、命令模式

先看定義:命令模式是把一個操作或者行為抽象為一個物件中,通過對命令的抽象化來使得發出命令的責任和執行命令的責任分隔開。命令模式的實現可以提供命令的撤銷和恢復功能

聽著還是很抽象,我們再舉一個例子,《破產姐妹》中熟悉的場景 ,顧客到餐廳點單,服務員MAX拿了訂單,放在訂單櫃檯,廚師Oleg根據訂單準備餐點。

分析下其中的過程,把訂單想象成一個用來請求準備餐點的物件,訂單物件可以被傳遞,服務員MAX負責傳遞物件,訂單的介面中只包含一個orderUp()方法,這個方法封裝了而準備餐點所需的動作,訂單內有一個“需要進行準備工作的物件”(也就是廚師Oleg)的引用,這一切都封裝起來,MAX甚至不需要知道訂單上有什麼,她只負責傳遞。

有沒有清楚一些,轉成類圖:

類圖中可以看出,其中的幾個角色:

  • 客戶角色(Client):發出一個具體的命令並設定其接受者。
  • 呼叫者(Invoker):持有一個命令物件,負責命令物件執行命令。
  • 命令角色(Command):宣告命令介面,並具備一個讓接收者執行的方法。
  • 具體命令角色(ConreteCommand):定義了動作和接收者之間的繫結關係,負責呼叫接收者的方法。
  • 接受者(Receiver):負責具體動作的執行。

三、程式碼實現 

命令介面:

//命令介面
public interface Command {
    //命令方法
    void execute();
}

命令接收者

//命令接收者(Receiver),電燈
public class Light {
    
private String name; public Light(String name){ this.name=name; } //開燈操作 public void on(){ System.out.println(name+":開燈!"); } //關燈操作 public void off(){ System.out.println(name+":關燈"); } }
繫結命令與接收者關係
//繫結命令與接收者關係ConreteCommand
public class LightOnCommand implements Command {
    Light light;
    public LightOnCommand(Light light){
        this.light=light;
    }
    //具體命令方法
    public void execute() {
       light.on();
    }
}

呼叫者(Invoker)

//命令模式的客戶(Invoker)
public class SimpleRemoteControl {
    //命令介面
    Command solt;
    public void setCommand(Command command){
        this.solt=command;
    }
    //命令方法
    public void buttonWasPressed(){
        solt.execute();
    }

}

執行:

private static void simpleControl() {
        //遙控器呼叫者
        SimpleRemoteControl control=new SimpleRemoteControl();
        //電燈
        Light light=new Light("客廳");
        //具體命令類
        LightOnCommand lightOnCommand=new LightOnCommand(light);
        //設定命令
        control.setCommand(lightOnCommand);
        //命令方法
        control.buttonWasPressed();


    }

結果:

這裡其實只實現了其中一個按鈕,讓我們來補充一些程式碼

增加多一個接收者:

//另外一個接受者吊燈
public class CeilingFan {
    private String name;

    public CeilingFan(String name){
        this.name=name;
    }

    public void on(){
        System.out.println(name+":開啟");
    }

    public void off(){
        System.out.println(name+":關閉");
    }
}
//吊燈的開燈命令
public class CeilingFanOffCommand implements Command {
    CeilingFan ceilingFan;
    public CeilingFanOffCommand(CeilingFan ceilingFan){
        this.ceilingFan=ceilingFan;
    }
    //具體命令方法
    public void execute() {
        ceilingFan.off();
    }
}

//吊燈的關燈命令
public class CeilingFanOnCommand implements Command {
    CeilingFan ceilingFan;
    public CeilingFanOnCommand(CeilingFan ceilingFan){
        this.ceilingFan = ceilingFan;
    }
    //具體命令方法
    public void execute() {
        ceilingFan.on();
    }
}

呼叫者遙控:

//遙控呼叫
public class RemoteControl {
    //申明命令陣列
    Command[] onCommands;
    Command[] offCommands;
    public RemoteControl(){
        onCommands=new Command[4];
        offCommands=new Command[4];
    }
    //設定命令
    public void setCommand(int solt,Command onCommand,Command offCommand){
        onCommands[solt]=onCommand;
        offCommands[solt]=offCommand;
    }
    //開啟按鈕
    public void onButtonWasPressed(int solt){
         onCommands[solt].execute();
    }
    //關閉按鈕
    public void offButtonWasPressed(int solt){
        offCommands[solt].execute();
    }

}

實際操作遙控:

private static void control() {
        RemoteControl remoteControl=new RemoteControl();
        Light roomLight=new Light("客廳燈");
        Light kitchLight=new Light("廚房燈");
        CeilingFan roomCeilingFan=new CeilingFan("客廳吊扇");
        CeilingFan kitchCeilingFan=new CeilingFan("廚房吊扇");
        //電燈相關命令
        LightOnCommand roomLightOnCommand=new LightOnCommand(roomLight);
        LightOnCommand kitchLightOnCommand=new LightOnCommand(kitchLight);
        LightOffCommand roomLightOffCommand=new LightOffCommand(roomLight);
        LightOffCommand kitchLightOffCommand=new LightOffCommand(kitchLight);
        //吊扇相關命令
        CeilingFanOnCommand roomCeilingFanOnCommand =new CeilingFanOnCommand(roomCeilingFan);
        CeilingFanOnCommand kitchCeilingFanOnCommand =new CeilingFanOnCommand(kitchCeilingFan);
        CeilingFanOffCommand roomCeilingFanOffCommand =new CeilingFanOffCommand(roomCeilingFan);
        CeilingFanOffCommand kitchCeilingFanOffCommand =new CeilingFanOffCommand(kitchCeilingFan);
        //將命令載入到卡槽中
        remoteControl.setCommand(0,roomLightOnCommand,roomLightOffCommand);
        remoteControl.setCommand(1,kitchLightOnCommand,kitchLightOffCommand);
        remoteControl.setCommand(2,roomCeilingFanOnCommand,roomCeilingFanOffCommand);
        remoteControl.setCommand(3,kitchCeilingFanOnCommand,kitchCeilingFanOffCommand);
        //使用遙控
        remoteControl.onButtonWasPressed(0);
        remoteControl.offButtonWasPressed(0);
        remoteControl.onButtonWasPressed(1);
        remoteControl.offButtonWasPressed(1);
        remoteControl.onButtonWasPressed(2);
        remoteControl.offButtonWasPressed(2);
        remoteControl.onButtonWasPressed(3);
        remoteControl.offButtonWasPressed(3);
    }

執行結果:

四、總結

在下面的情況下可以考慮使用命令模式:

  1. 系統需要支援命令的撤銷(undo)。命令物件可以把狀態儲存起來,等到客戶端需要撤銷命令所產生的效果時,可以呼叫undo方法吧命令所產生的效果撤銷掉。命令物件還可以提供redo方法,以供客戶端在需要時,再重新實現命令效果。
  2. 系統需要在不同的時間指定請求、將請求排隊。一個命令物件和原先的請求發出者可以有不同的生命週期。意思為:原來請求的發出者可能已經不存在了,而命令物件本身可能仍是活動的。這時命令的接受者可以在本地,也可以在網路的另一個地址。命令物件可以序列地傳送到接受者上去。
  3. 如果一個系統要將系統中所有的資料訊息更新到日誌裡,以便在系統崩潰時,可以根據日誌裡讀回所有資料的更新命令,重新呼叫方法來一條一條地執行這些命令,從而恢復系統在崩潰前所做的資料更新。
  4. 系統需要使用命令模式作為“CallBack(回撥)”在面向物件系統中的替代。Callback即是先將一個方法註冊上,然後再以後呼叫該方法。

優點: 1、降低了系統耦合度。 2、新的命令可以很容易新增到系統中去。

缺點:使用命令模式可能會導致某些系統有過多的具體命令類。

 另附原始碼地址:https://gitee.com/yuanqinnan/pattern