最近在實踐服務熔斷時用到了Hystrix這個框架,以爲裏面的設計思想挺值得學習,決定深刻研究一番。在學習過程當中,發現不少名詞仍是不太熟悉,仍是須要有一些技術準備才能繼續深刻,第一個遇到的是設計模式中的命令模式,命令模式這個設計模式以前也學過,可是因爲沒有實踐機會,因此很快就忘記,如今有機會來實戰一次,溫故而知新。git
直接看維基百科上的定義,Command Pattern(命令模式)github
在面對對象編程中,命令模式是一種行爲模式,其中對象用於封裝執行動做或稍後觸發事件所需的全部信息。這些信息包括方法名稱,擁有該方法的對象以及方法參數的值。(來自維基百科)編程
看概念直接解釋名詞仍是比較模糊,先來看看一個具體的實例,而後再繼續解釋其中的名詞模式的定義。設計模式
需求:爲一個電器遙控器編程功能實現,一共開燈、關燈、開風扇、關風扇、風扇調高檔、風扇調低檔等6按鈕。框架
若是不用命令模式,實現可能會是在按下按鈕時,傳遞具體的指令給遙控對象,遙控對象根據具體的指令實例化對象,並調用對應的操做。學習
遙控器的實現代碼以下所示:this
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();
}
}
}
複製代碼
這裏有個比較繁瑣的一點是遙控器對象須要根據指令來調用對象的方法,若是須要爲遙控器新增功能,好比對燈增長調節檔數的功能,那麼就須要在遙控器裏增長判斷,判斷是否屬於燈光調檔的指令,而後才能完成工做。這樣的缺點是遙控器始終要關注須要調用的外部服務,若是新增服務時須要改動代碼,這樣違背了面向接口編程的原則,同時代碼也較難維護。spa
改成使用命令模式,實現的方式是將服務的實現封裝到一個對象委託出去,由命令對象來實現具體的調用。只須要將具體的執行指令遙控器,按下按鈕後就會開始執行相應的指令,無需入侵業務代碼。具體實現代碼以下:設計
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圖及代碼能夠看出,這樣一來,遙控器只須要實現的是發送執行指令就能夠,執行什麼指令,就由命令對象去關注這一點,具體要怎麼執行,交給接收者去決定。當須要新增指令時,只須要新增命令對象,不須要對遙控器對象進行修改,實現了面向接口編程。同時也能夠看到,請求者與接收者經過封裝命令對象進行了解耦,當增長指令時,只須要增長命令對象,設置到遙控器,便可實現增長指令的需求,這就是命令模式中可使用不一樣請求參數化對象的意思。3d
在命令模式中,有四種角色:命令、接收者、調用者以及客戶端。
命令知道具體接收者,也是接收者具體方法的調用方。接收者方法參數的值保存在命令。
執行具體方法的接收者對象經過組合的方式保存在命令對象中。
接收者執行具體的調用當命令實例調用「執行」方法時。
調用者對象知道如何執行命令,頗有可能還會記錄下執行的命令,可是調用者對具體要執行什麼命令一無所知,僅僅知道的是要調用的是一個命令接口。
調用者對象、命令對象、接收者對象,統統由客戶端持有,客戶端決定它要分配給命令對象的接收者對象,以及要分配給調用者的命令對象。
客戶端決定何時執行命令,客戶端經過傳遞命令對象給調用者對象來執行命令。
使用命令對象,使得更容易構建一些在無需知道方法的類和方法參數的狀況下,須要在選擇時委託、排序或執行方法調用的通用組件。
使用調用者對象,容許方便地實現命令執行的簿記,以及實現由調用者對象管理的命令的不一樣模式,而不須要客戶端知道簿記或模式的存在。
**可使用請求配置對象(調用者)。**使代碼可擴展,當須要增長實現時,只需繼承/實現"命令」,而後將具體執行的代碼封裝爲對象,設置到調用者便可。即便用封裝好的請求來配置調用者對象,無需入侵調用者的代碼。
解耦請求者和實現者,請求者不須要知道關於具體的實現者的信息以及如何實現,只須要知道的是要執行某個命令,由具體的命令去關心實現者是誰,如何調用。
使用命令封裝了請求,好比開燈命令。
命令模式描述的實現: 定義分開的命令對象來封裝請求。 類將請求委託給命令對象而不是直接實現請求。
命令模式中使用不一樣請求參數化對象的意思是:命令已經被封裝爲一個對象了,所以能夠將命令設置到其餘對象的屬性中,這樣其餘對象就擁有更豐富的功能了。好比一個按鈕能夠是開燈的,但也能夠改造內部的線路讓按鈕是控制音量的。
命令模式的缺點就是類的數量太多,由於每個命令都須要新建一個類。
當要調用外部服務時,每每不知道具體的外部服務是誰,也不知道具體作了什麼操做,要作的只是指定具體的外部服務,具體要作何時怎麼執行該操做,由接受者決定,發送者只是知道發出請求就能夠。
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是一門封裝性較強的語言,在不少框架和庫,都是經過設計模式來提高代碼的優雅性和可維護性,可是單純地學習設計模式也較難好好掌握,甚至會常常遺忘核心原理,在須要的時候帶着目的去學習是較好的掌握方式。
原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。
若是本文對你有幫助,請點個贊吧,謝謝^_^
更多精彩內容,請關注我的公衆號。