淺談設計模式 - 命令模式(七)

淺談設計模式 - 命令模式(七)

前言:

命令模式也是一種比較常見的行爲型模式,能夠想象咱們的手機智能遙控器,經過按動按鈕的形式開啓各類傢俱,說白了,就是將一系列的請求命令封裝起來,不直接調用真正執行者的方法,這樣比較好擴展。須要注意的是命令模式和策略模式類似,因此有時候可能容易弄混,這篇文章將會詳細介紹命令模式java

文章目的:

  1. 瞭解命令的模式的特色
  2. 簡單對比命令模式和策略模式
  3. 命令模式的優缺點總結

什麼是命令模式?

解釋:把「請求」封裝爲對應的對象,使用不一樣的請求參數化對象,命令模式支持撤銷撤銷的操做設計模式

命令模式是一種行爲型模式,實現了接口調用對象和返回對象,用命令對象做爲橋樑實現調用者和具體實現者之間的解耦和交互。數組

命令模式的特色:

  • 將發出請求的對象和執行請求的對象解耦
  • 調用者能夠自由定義命令參數進行自由的組合
  • 命令能夠用來實現日誌或者事務系統(undo操做)

命令模式結構圖:

下面根據命令模式的定義,以及上面對於命令模式的理解,構建具體的結構圖服務器

+ Client 客戶端:客戶端須要建立具體的命令類,而且經過發送請求給執行者調用具體的對象,發送方和接收方不存在關聯,統一由命令對象進行鏈接。ide

+ Invoker 執行者:請求的發送者,負責將請求分發給具體的命令實現類,由實現類調用實際的執行者進行執行操做單元測試

+ Command 接口:命令接口,定義命令的規範測試

+ ConcreteCommand 命令接口實現類:實現命令的同時組合具體對象。this

+ ConcreteObject 具體實現類:定義截圖的實現生產對象。spa

+ Receive 執行者:請求的真正執行者,能夠是任意對象,一般以 組合形式出如今執行者的內部設計

命令模式的理解

這裏參考《Head firtst設計模式》的案例,模擬具體的交互流程

對象村餐廳交互過程

咱們到餐廳點餐,通常會經歷以下的流程

  1. 客人負責下訂單,由服務員接受訂單
  2. 服務器接收訂單,調用訂單櫃檯的下訂單的方法,不須要關注細節
  3. 訂單櫃檯通知廚師進行生產
  4. 廚師生產訂單物品以後,交給服務員上菜

根據上面的步驟利用僞代碼的表現以下:

  • createCommandObject() 構建命令對象
  • setCommand() 傳遞命令
  • execute() 命令執行
  • action1()action2() 執行者實際執行

交互流程圖

咱們根據上面的交互過程介紹,構建具體的交互流程圖,咱們能夠看到裏面有角色:客人服務員訂單櫃檯廚師,他們自己並無關聯,而是經過餐廳的形式彼此產生了具體的關聯,同時咱們對比上面的結構圖,看下對象村餐廳對應的結構圖:

下面根據結構圖說一下各類角色的職責:

客人:至關於client客戶端,負責指揮服務員進行下單的操做。

服務員:充當請求的發送者,接受客戶端的請求,調用下訂單的接口到具體的訂單櫃檯,可是不須要關心具體的細節,只具有下訂單這一個操做

訂單櫃檯:經過服務員傳遞的訂單,安排廚師執行具體的任務

廚師:根據訂單櫃檯的訂單作菜,將結果返回給服務員(或客人)

咱們從上面的角色圖再來看具體的命令模式定義,能夠看到基本都是一一對應的狀況。

命令模式和策略模式的對比

命令模式和策略模式的結構圖有些許的相似,下面咱們來對比看一下這兩張圖的異同:

策略模式結構圖:

策略模式

命令模式結構圖:

命令模式

相同點:

  1. 命令模式經過定義命令規範接口,由子類實現命令的執行細節,策略一樣定義策略行爲同時用子類實現不一樣的策略功能
  2. 命令模式和策略都解耦了請求的發送者和執行者

不一樣點:

  1. 命令模式利用了命令組合執行對象的形式執行實現具體實現,而策略模式依靠上下文對象進行切換
  2. 策略模式針對某個對象實現不一樣的策略效果,而命令模式關注請求發送者和實現者之間的業務解耦組合

實戰

模擬場景:

​ 此次的案例仍是模擬《Head First》設計模式的當中對於遙控器遙控電器的一個案例,咱們定義以下的內容:

遙控器:命令的發送方,負責根據不一樣的操做按鈕調用不一樣的設備工做,生成具體的命令對象調用接口執行具體的命令

命令接口:負責定義命令的實現規範,充當遙控器裏面的每個按鈕,對應都有具體的實現

命令實現類:負責實現命令的接口,同時調用具體的實現對象執行命令

實現對象:命令的真正執行者,通常夬在命令實現類的內部,好比電視,燈泡等

不適用設計模式

在不使用設計模式的狀況下,咱們一般經過對象組合的形式組合不一樣的實體對象執行命令,下面經過一些簡單的代碼說明一下設計的弊端:

// 燈泡
public class Light {

    public void on(){
        System.out.println("打開燈光");
    }

    public void off(){
        System.out.println("關閉燈光");
    }

}
// 電視機
public class Television {

    public void on(){
        System.out.println("打開電視");
    }

    public void off(){
        System.out.println("關閉電視");
    }

}
// 遙控器
public class RemoteControl {

    private Light light;

    private Television television;

    public RemoteControl(Light light, Television television) {
        this.light = light;
        this.television = television;
    }

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

    public void button2(){
        television.on();
    }
}
// 單元測試
public class Main {

    public static void main(String[] args) {
        Television television = new Television();
        Light light = new Light();
        RemoteControl remoteControl = new RemoteControl(light, television);
        remoteControl.button1();
        remoteControl.button2();

    }
}/*運行結果:
打開燈光
打開電視
*/

從上面的簡單代碼能夠看到,若是咱們繼續增長電器,同時增長方法,不只會致使遙控器要隨着電器的改動不斷改動,同時每次新增一個電器,遙控器要進行相似「註冊」的行爲,須要將電器接入到遙控器,這樣顯然是不符合邏輯的,由於咱們都知道,遙控器是單純的指揮者,他不參與任何命令的操做細節,同時雖然真正工做的方法是具體對象的方法,可是這種形式相似將電器「塞」到了遙控器的內部執行,這樣也是存在問題,咱們下面須要修改一下這種嚴重耦合的設計。

使用命令模式改寫:

咱們按照命令模式的結構圖,改寫案例,咱們須要定義下面的類和對應的接口:

+ RemoteControl 遙控器
+ Command(接口) 命令規範接口,用於接入到遙控器內部
+ LightCommandConcrete 控制電器的亮滅命令實現
+ SwitchCommandConcrete 控制電器的開關命令實現
+ Light 燈泡
+ Television 電視機

首先,咱們定義命令的接口,定義接口的規範方法。而後定義實現子類實現不一樣命令的操做效果,在命令實現類的內部,咱們組合實際執行對象,在接口方法調用實際的對象方法,這樣就作到了執行者和發送者之間的解耦。

接着,咱們改寫控制器,他不在持有任何實際的對象方法,經過組合命令的接口,讓客戶端傳入實現的功能,經過這種方式,遙控器不在須要依賴具體的電器實現調用具體方法,而是關注命令的接口方法,一切的細節都在命令的子類內部。

下面代碼是依照命令模式進行的最簡單的一個實現。

// 命令接口
public interface Command {

    /**
     * 接口備份
     */
    void execute();

}

public class LightCommandConcrete implements Command {

    private Light light = new Light();

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

public class SwitchCommandConcrete implements Command{
    private Television television = new Television();

    @Override
    public void execute() {
        television.on();
    }
}

// 遙控器
public class RemoteControl {

    private Command command;

    public RemoteControl(Command command) {
        this.command = command;
    }

    public void execute(){
        command.execute();
    }

    public Command getCommand() {
        return command;
    }

    public void setCommand(Command command) {
        this.command = command;
    }
}

public class Main {
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl(new LightCommandConcrete());
        remoteControl.execute();
        remoteControl.setCommand(new SwitchCommandConcrete());
        remoteControl.execute();
    }
}

通過上面的代碼改造,咱們成功上面的代碼改造爲命令模式的代碼,使用設計模式以後,咱們將調用者和實際執行者進行了解耦,控制器不須要知道執行的細節,只須要組合本身的命令接口,由客戶端指定但願實現的內容,執行相對應的具體命令。

案例的額外擴展:

下面是對應案例如何進行後續的擴展,對於這部份內容文章篇幅有限,同時本着不重複造輪子的理念,請閱讀《Head First設計模式》關於命令模式這一個章節,同時安利一下這本書,很是通俗易懂的講解設計模式,對於我的的提高幫助很大。

對於上面的設計,如何加入Undo的操做?

Undo是一個很常見的功能,若是想要讓Undo的操做集成到案例內部,須要按照以下的步驟進行操做:

  1. Command 接口增長Undo的操做,讓全部命令支持undo
  2. 在控制器記錄最後一個命令的執行對象,記錄最後的操做命令,實現控制器支持undo操做
  3. 具體Command實現增長對於undo()方法調用,而且根據實際的組合對象調用方法
  4. 具體實現類實現undo()操做的具體行爲效果。

若是undo裏面,存在一些變量如何處理?

在命令的實現類內部,須要增長一個最後變量值的記錄,用於記錄當前最後一步操做的屬性和變量

如何作到宏命令?

實現一個命令類,經過組合數組或者堆棧組合多個其餘命令對象,經過for循環的形式依次調用。

undo也可使用這種方式進行調用的,可是要注意**調用的順序相反

命令模式的優缺點:

優勢:

  • 命令模式實現了請求發送方和實現方解耦,不管是發送方仍是接收方都不須要
  • 命令模式能夠實現不一樣實現對象的自由組合,經過命令組合能夠實現一連串簡單功能

缺點:

  • 和策略模式相似,命令模式很容易形成子類的膨脹

總結:

​ 命令模式是一種很是常見的設計模式,這種模式更多的關注點是解耦請求的發送方和實現方,命令模式在系統設計中使用仍是十分常見的,是一種值得關注的設計模式。

相關文章
相關標籤/搜索