命令模式【command】
- 什麼是命令模式
將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤消的操作。稍後我會以代碼加文字的形式給大家解釋清楚。別名(動作( Action ),事務(Transation )) - 什麼場景會用到命令模式
我們模擬一下這個場景。用戶去一個餐廳,可以點菜,並且可以取消之前點過的菜,原型圖如下:
流程如下,用戶下達指定,告訴服務員需要做什麼菜,然後服務員再去通知相應的廚子做菜。我們從程序角度怎麼去設計呢,如果大家運用到設計模式,肯定是要出於對設計上的考慮,所以第一我們要降低各個對象之間的耦合度,從上面的圖我們不難看出,我們有三個對象需要分開來,第一是客戶,是下達指定的人,第二就是服務員,是傳遞指令的人,第三是廚師,是執行指令的人。第二,我們需要考慮到擴展性,比如廚房現在推出了新的菜系,怎麼在不修改原有代碼的情況下進行擴展。命令模式通過將請求本身變成一個對象來使發出命令的對象可以向未指定的應用對象提出請求。顧名思義,命令模式,主要是請求對象,所以我們主要是在服務員上做思考。代碼如下:
我們先定義後廚:
public interface Cook {
void makeTomatoEggSoup();
void makeNaanBread();
}
public class Chef implements Cook {
@Override
public void makeTomatoEggSoup() {
System.out.println("嚐嚐大廚做的西紅柿蛋湯");
}
@Override
public void makeNaanBread() {
System.out.println("嚐嚐大廚做的印度飛餅");
}
}
public class RookieChef implements Cook {
@Override
public void makeTomatoEggSoup() {
System.out.println("嚐嚐新來的廚師做的西紅柿蛋湯");
}
@Override
public void makeNaanBread() {
System.out.println("嚐嚐新來的廚師做的印度飛餅");
}
}
定義菜譜
public abstract class Food {
protected Cook cook;
public Food(Cook cook) {
this.cook = cook;
}
public abstract void make();
}
public class NaanBread extends Food {
public NaanBread(Cook cook) {
super(cook);
}
@Override
public void make() {
this.cook.makeNaanBread();
}
@Override
public String toString() {
return "NaanBread";
}
}
public class TomatoEggSoup extends Food {
public TomatoEggSoup(Cook cook) {
super(cook);
}
@Override
public void make() {
this.cook.makeTomatoEggSoup();
}
@Override
public String toString() {
return "TomatoEggSoup";
}
}
定義服務員
public class Waiter {
private List<Food> foods = new ArrayList<>();
public synchronized void add(Food food) {
foods.add(food);
System.out.println(String.format("您好,您確認點這個%s嗎,我幫您記錄下來.", food));
}
public synchronized void cancel(Food food) {
foods.remove(food);
System.out.println(String.format("您好,您確認取消這個%s嗎,我幫您記錄下來.", food));
}
public void execute() {
foods.forEach(Food::make);
}
}
測試方法
/**
* 我需要大廚幫我做一份印度飛餅
* 然後我聽說最近有個新來的廚師,做的西紅柿蛋湯味道不錯,也給我來一份
* 算了,最近西紅柿過敏,我還是不要了
* 今天我就吃印度飛餅吧
*/
public static void main(String[] args) {
Waiter waiter = new Waiter();
Food naanBread = new NaanBread(new Chef());
Food tomatoEggSoup = new TomatoEggSoup(new RookieChef());
waiter.add(naanBread);
waiter.add(tomatoEggSoup);
waiter.cancel(tomatoEggSoup);
waiter.execute();
}
我們不難發現,運用這個程序可以很好的去實現用戶點餐,比如我點餐和取消,都是通過服務員去處理的,廚師只管去處理菜單上面有的菜,並且如果我後面添加了其他的菜,也只用去添加新的方法,不需要去改動現有的邏輯。
看了這段代碼,是不是對命令模式有了一個很清晰的認識了
- 適用性
當你有如下需求時,可使用Command模式:
• 在不同的時刻指定、排列和執行請求。一個 Command對象可以有一個與初始請求無關
的生存期。如果一個請求的接收者可用一種與地址空間無關的方式表達,那麼就可將負
責該請求的命令對象傳送給另一個不同的進程並在那兒實現該請求。
• 支持取消操作。Command的Execute操作可在實施操作前將狀態存儲起來,在取消操作時
這個狀態用來消除該操作的影響。Command接口必須添加一個Unexecute操作,該操作
取消上一次Execute調用的效果。執行的命令被存儲在一個歷史列表中。可通過向後和
向前遍歷這一列表並分別調用Unexecute和Execute來實現重數不限的“取消”和“重
做”。
• 支持修改日誌,這樣當系統崩潰時,這些修改可以被重做一遍。在Command接口中添
加裝載操作和存儲操作,可以用來保持變動的一個一致的修改日誌。從崩潰中恢復的過
程包括從磁盤中重新讀入記錄下來的命令並用Execute操作重新執行它們。
• 用構建在原語操作上的高層操作構造一個系統。這樣一種結構在支持事務(transation)
的信息系統中很常見。一個事務封裝了對數據的一組變動。Command模式提供了對事
務進行建模的方法。Command有一個公共的接口,使得你可以用同一種方式調用所有
的事務。同時使用該模式也易於添加新事務以擴展系統。
相關參考:
<大話設計模式>,<設計模式,可複用面向對象軟件的基礎>
源碼在此