聊聊Hystrix中的命令模式

hoohack發表於2019-03-02

最近在實踐服務熔斷時用到了Hystrix這個框架,覺得裡面的設計思想挺值得學習,決定深入研究一番。在學習過程中,發現很多名詞還是不太熟悉,還是需要有一些技術準備才能繼續深入,第一個遇到的是設計模式中的命令模式,命令模式這個設計模式之前也學過,但是由於沒有實踐機會,所以很快就忘記,現在有機會來實戰一次,溫故而知新。

定義

直接看維基百科上的定義,Command Pattern(命令模式)

在面對物件程式設計中,命令模式是一種行為模式,其中物件用於封裝執行動作或稍後觸發事件所需的所有資訊。這些資訊包括方法名稱,擁有該方法的物件以及方法引數的值。(來自維基百科)

看概念直接解釋名詞還是比較模糊,先來看看一個具體的例項,然後再繼續解釋其中的名詞模式的定義。

舉個例子

需求:為一個電器遙控器程式設計功能實現,一共開燈、關燈、開風扇、關風扇、風扇調高檔、風扇調低檔等6按鈕。

如果不用命令模式,實現可能會是在按下按鈕時,傳遞具體的指令給遙控物件,遙控物件根據具體的指令例項化物件,並呼叫對應的操作。

遙控器的實現程式碼如下所示:

public class RemoteControl {

    private Light light;

    private Fan fan;

    public RemoteControl() {
        light = new Light();
        fan = new Fan();
    }

    public void buttonPressed(String buttonName) {
        if (buttonName.equals(light.getButtonTurnOnName())) {
            light.on();
        } else if (buttonName.equals(light.getButtonTurnOffName())) {
            light.off();
        } else if (buttonName.equals(fan.getButtonTurnOnName())) {
            fan.on();
        } else if (buttonName.equals(fan.getButtonTurnOffName())) {
            fan.off();
        } else if (buttonName.equals(fan.getButtonSetSpeedUpName())) {
            fan.speedUp();
        } else if (buttonName.equals(fan.getButtonSetSpeedDownName())) {
            fan.speedDown();
        }
    }
}
複製程式碼

這裡有個比較繁瑣的一點是遙控器物件需要根據指令來呼叫物件的方法,如果需要為遙控器新增功能,比如對燈增加調節檔數的功能,那麼就需要在遙控器裡增加判斷,判斷是否屬於燈光調檔的指令,然後才能完成工作。這樣的缺點是遙控器始終要關注需要呼叫的外部服務,如果新增服務時需要改動程式碼,這樣違背了面向介面程式設計的原則,同時程式碼也較難維護。

改為使用命令模式,實現的方式是將服務的實現封裝到一個物件委託出去,由命令物件來實現具體的呼叫。只需要將具體的執行指令遙控器,按下按鈕後就會開始執行相應的指令,無需入侵業務程式碼。具體實現程式碼如下:

public class RemoteControl {

    private Command command;

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

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

}

public class RemoteControlRun {

    public static void main(String[] args) {
        Light light = new Light();


        RemoteControl remoteControl = new RemoteControl();
        remoteControl.command(new LightOnCommand(light));
        remoteControl.buttonPressed();

        remoteControl.command(new LightOffCommand(light));
        remoteControl.buttonPressed();
    }

}
複製程式碼

使用命令模式實現此次程式碼的UML圖如下,結合UML圖及程式碼可以看出,這樣一來,遙控器只需要實現的是傳送執行指令就可以,執行什麼指令,就由命令物件去關注這一點,具體要怎麼執行,交給接收者去決定。當需要新增指令時,只需要新增命令物件,不需要對遙控器物件進行修改,實現了面向介面程式設計。同時也可以看到,請求者與接收者通過封裝命令物件進行了解耦,當增加指令時,只需要增加命令物件,設定到遙控器,即可實現增加指令的需求,這就是命令模式中可以使用不同請求引數化物件的意思。

遙控實現UML圖

理解命令模式

在命令模式中,有四種角色:命令、接收者、呼叫者以及客戶端。

命令知道具體接收者,也是接收者具體方法的呼叫方。接收者方法引數的值儲存在命令。

執行具體方法的接收者物件通過組合的方式儲存在命令物件中。

接收者執行具體的呼叫當命令例項呼叫“執行”方法時。

呼叫者物件知道如何執行命令,很有可能還會記錄下執行的命令,但是呼叫者對具體要執行什麼命令一無所知,僅僅知道的是要呼叫的是一個命令介面。

呼叫者物件、命令物件、接收者物件,通通由客戶端持有,客戶端決定它要分配給命令物件的接收者物件,以及要分配給呼叫者的命令物件。

客戶端決定什麼時候執行命令,客戶端通過傳遞命令物件給呼叫者物件來執行命令。

使用命令物件,使得更容易構建一些在無需知道方法的類和方法引數的情況下,需要在選擇時委託、排序或執行方法呼叫的通用元件。

使用呼叫者物件,允許方便地實現命令執行的簿記,以及實現由呼叫者物件管理的命令的不同模式,而不需要客戶端知道簿記或模式的存在。

命令模式解決的問題

**可以使用請求配置物件(呼叫者)。**使程式碼可擴充套件,當需要增加實現時,只需繼承/實現"命令”,然後將具體執行的程式碼封裝為物件,設定到呼叫者即可。即使用封裝好的請求來配置呼叫者物件,無需入侵呼叫者的程式碼。

解耦請求者和實現者,請求者不需要知道關於具體的實現者的資訊以及如何實現,只需要知道的是要執行某個命令,由具體的命令去關心實現者是誰,如何呼叫。

使用命令封裝了請求,比如開燈命令。

命令模式描述的實現: 定義分開的命令物件來封裝請求。 類將請求委託給命令物件而不是直接實現請求。

命令模式中使用不同請求引數化物件的意思是:命令已經被封裝為一個物件了,因此可以將命令設定到其他物件的屬性中,這樣其他物件就擁有更豐富的功能了。比如一個按鈕可以是開燈的,但也可以改造內部的線路讓按鈕是控制音量的。

缺點

命令模式的缺點就是類的數量太多,因為每一個命令都需要新建一個類。

模式結構圖

命令模式UML圖

當要呼叫外部服務時,往往不知道具體的外部服務是誰,也不知道具體做了什麼操作,要做的只是指定具體的外部服務,具體要做什麼時候怎麼執行該操作,由接受者決定,傳送者只是知道發出請求就可以。

Hystrix中的命令模式

Hystrix使用了命令模式,以Hystrix為例,再介紹一個使用示例。 應用依賴兩個服務,每個服務都提供了獲取單個資料和資料列表的介面,如果需要對服務進行熔斷控制,不使用命令模式的情況下的程式碼如下:

public class SimpleHystrix {

    private AService aService;

    private BService bService;

    public SimpleHystrix() {
        this.aService = new AService();
        this.bService = new BService();
    }

    public void call(String callName, int param) {
        if (callName.equals("aServiceGetSingleData")) {
            try {
                aService.getSingleData(param);
            } catch (Exception e) {
                System.out.println("aService getSingleData exception");
            }
        } else if (callName.equals("aServiceGetList")) {
            try {
                aService.getList();
            } catch (Exception e) {
                System.out.println("aService getList exception");
            }
        } else if (callName.equals("bServiceGetSingleData")) {
            try {
                bService.getSingleData(param);
            } catch (Exception e) {
                System.out.println("bService getSingleData exception");
            }
        } else if (callName.equals("bServiceGetList")) {
            try {
                bService.getList();
            } catch (Exception e) {
                System.out.println("bService getList exception");
            }
        }
    }
}
複製程式碼

很明顯,如果還要新增一個服務的話,則需要多加一個服務,使用命令模式封裝後的程式碼如下:

public class SimpleHystrix {

    private SimpleHystrixCommand simpleHystrixCommand;

    public void setSimpleHystrixCommand(SimpleHystrixCommand simpleHystrixCommand) {
        this.simpleHystrixCommand = simpleHystrixCommand;
    }

    public void call() {
        simpleHystrixCommand.execute();
    }

}
複製程式碼

程式碼結構非常清晰,需要增加服務也很簡單,無需入侵業務程式碼,只需要增加一個繼承Command的類,然後在execute方法實現對應服務的呼叫以及其他操作即可。

本次用到的demo程式碼可以在我的github上找到: github.com/hoohack/Des…

總結

以上就是命令模式的介紹,個人覺得,想要看懂Java實現的框架或者庫的原始碼,就先要了解設計模式,畢竟Java是一門封裝性較強的語言,在很多框架和庫,都是通過設計模式來提升程式碼的優雅性和可維護性,但是單純地學習設計模式也較難好好掌握,甚至會經常遺忘核心原理,在需要的時候帶著目的去學習是較好的掌握方式。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

如果本文對你有幫助,請點個贊吧,謝謝^_^

更多精彩內容,請關注個人公眾號。

聊聊Hystrix中的命令模式

相關文章