使用函數(shù)抽象行為進(jìn)行優(yōu)化
將這些做飯的命令抽象成函數(shù)接口,然后指定一個(gè)執(zhí)行者,這樣的接口在Java8 4大函數(shù)接口中屬于Consumer接口,也就是消費(fèi)者接口,下面就用Consumer接口進(jìn)行行為抽象
廚房類依舊不變
public class Kitchen {
public void beefRice(){
System.out.println("一份牛肉飯做好了!");
}
public void scrambledEggsWithTomatoes(){
System.out.println("一份西紅柿炒雞蛋做好了!");
}
public void beerDuck(){
System.out.println("一份啤酒鴨做完啦!");
}
}
服務(wù)員類也很容易,原先裝的是一個(gè)個(gè)命令對(duì)象,現(xiàn)在直接一點(diǎn),直接將行為放進(jìn)去
public class Waiter {
/**
* 此時(shí)隊(duì)列裝載的不再是命令對(duì)象了,而是更直接的廚房類的行為
*/
private final Queue<Consumer<Kitchen>> orders;
public Waiter() {
orders = new ArrayDeque<>();
}
/**
* 添加訂單
* @param kitchenAction 廚房的具體行為
*/
public final void setOrders(Consumer<Kitchen> kitchenAction) {
System.out.printf("添加訂單成功! 訂單時(shí)間: %s \n", LocalDateTime.now());
orders.add(kitchenAction);
}
/**
* 這里增加一個(gè)執(zhí)行者參數(shù),來對(duì)隊(duì)列中的行為進(jìn)行操作
* @param kitchen 執(zhí)行者,用于執(zhí)行隊(duì)列中的行為
*/
public final void notifyKitchen(Kitchen kitchen) {
while (orders.peek() != null) {
orders.peek().accept(kitchen);
orders.remove();
}
}
}
客戶端代碼,簡單、清晰的驚人,代碼自帶注釋效果,無論是簡短性還是可閱讀性,都比之前的要好上很多,中間的營業(yè)代碼很直觀,直接閱讀代碼就很清楚的看到添加了哪些行為到訂單隊(duì)里中
public class Client {
public static void main(String[] args) {
//準(zhǔn)備廚房,服務(wù)員,菜單命令工作
Kitchen kitchen = new Kitchen();
Waiter waiter = new Waiter();
//開始營業(yè)
System.out.println("=======================添加訂單環(huán)節(jié)=======================");
// 顧客:服務(wù)員 一份牛肉飯!
waiter.setOrders(Kitchen::beefRice);
// 顧客:服務(wù)員 一份啤酒鴨!
waiter.setOrders(Kitchen::beerDuck);
// 顧客:服務(wù)員 一份西紅柿炒雞蛋!
waiter.setOrders(Kitchen::scrambledEggsWithTomatoes);
// 顧客:服務(wù)員 兩份啤酒鴨!
waiter.setOrders(Kitchen::beerDuck);
waiter.setOrders(Kitchen::beerDuck);
System.out.println("==========服務(wù)員將訂單送至廚房,廚房按照訂單順序開始做飯=========");
//服務(wù)員通知廚房按照訂單順序開始做
waiter.notifyKitchen(kitchen);
}
}
輸出結(jié)果
=======================添加訂單環(huán)節(jié)=======================
添加訂單成功! 訂單時(shí)間: 2017-10-16T03:19:40.003
添加訂單成功! 訂單時(shí)間: 2017-10-16T03:19:40.019
添加訂單成功! 訂單時(shí)間: 2017-10-16T03:19:40.020
添加訂單成功! 訂單時(shí)間: 2017-10-16T03:19:40.020
添加訂單成功! 訂單時(shí)間: 2017-10-16T03:19:40.021
==========服務(wù)員將訂單送至廚房,廚房按照訂單順序開始做飯=========
一份牛肉飯做好了!
一份啤酒鴨做完啦!
一份西紅柿炒雞蛋做好了!
一份啤酒鴨做完啦!
一份啤酒鴨做完啦!
Process finished with exit code 0
到這里,已經(jīng)沒有其他類的代碼了!類的數(shù)量由原先的7個(gè)變成了3個(gè),并且由于服務(wù)員類(Invoke角色)依然存在,原先的解耦復(fù)合控制擴(kuò)展等優(yōu)點(diǎn),一個(gè)都沒少,與此同時(shí)客戶端的代碼也清爽了不少。試想一下,假如現(xiàn)在廚房有100道做菜的方法,按照原先的方法實(shí)現(xiàn)的類的數(shù)量應(yīng)該是3(客戶端+廚房+服務(wù)員) + 1(抽象命令接口) + 100(具體命令接口) = 104個(gè)類,而采用lambda之后,依舊只需要三個(gè)類!并且原先的優(yōu)點(diǎn)完全保留了下來。
函數(shù)補(bǔ)充優(yōu)化(update 17.10.24)
在整理關(guān)于方法引用轉(zhuǎn)換函數(shù)接口資料的時(shí)候忽然覺得命令模式優(yōu)化這里似乎可以再稍微變動(dòng)一下使得代碼呈現(xiàn)鏈?zhǔn)?之前也有過類似的想法,但是沒想到門路,今天想到了,就補(bǔ)充一下)
上文的代碼其實(shí)已經(jīng)成型了,這里做的優(yōu)化是將多次調(diào)用設(shè)置成鏈?zhǔn)秸{(diào)用法。
鏈?zhǔn)秸{(diào)用一
只需要將waiter類的setOrders方法的返回值設(shè)置為this本身即可。
public final Waiter setOrders(Consumer<Kitchen> kitchenAction) {
System.out.printf("添加訂單成功! 訂單時(shí)間: %s \n", LocalDateTime.now());
orders.add(kitchenAction);
return this;
}
客戶端鏈?zhǔn)秸{(diào)用
public class Client {
public static void main(String[] args) {
//準(zhǔn)備廚房,服務(wù)員,菜單命令工作
Kitchen kitchen = new Kitchen();
Waiter waiter = new Waiter();
//開始營業(yè)
System.out.println("=======================添加訂單環(huán)節(jié)=======================");
// 顧客:服務(wù)員 一份牛肉飯!
// 顧客:服務(wù)員 一份啤酒鴨!
// 顧客:服務(wù)員 一份西紅柿炒雞蛋!
// 顧客:服務(wù)員 兩份啤酒鴨!
// 顧客:服務(wù)員 兩份啤酒鴨!
//這里通過setOrders完成鏈?zhǔn)秸{(diào)用,和之前的效果完全一樣,但是提高了可讀性與間接性
waiter.setOrders(asConsumer(Kitchen::beefRice))
.setOrders(Kitchen::beerDuck)
.setOrders(Kitchen::scrambledEggsWithTomatoes)
.setOrders(Kitchen::beerDuck)
.setOrders(Kitchen::beerDuck);
System.out.println("==========服務(wù)員將訂單送至廚房,廚房按照訂單順序開始做飯=========");
//服務(wù)員通知廚房按照訂單順序開始做
waiter.notifyKitchen(kitchen);
}
}
輸出結(jié)果和上面一樣,這里不再顯示
鏈?zhǔn)秸{(diào)用二
這里是通過一個(gè)方法引用的包裝器,然后通過function接口的andThen方法鏈?zhǔn)降闹v訂單存入訂單隊(duì)列中。
- 這里與上文的鏈?zhǔn)絻?yōu)化一的是有區(qū)別的,區(qū)別在于,上文中無論是普通的setOrders還是return this 鏈?zhǔn)降膕etOrders的 日志信息是會(huì)輸出5條的(進(jìn)行了5次的setOrders動(dòng)作),而這里只會(huì)輸出一條,原因是這里的一條動(dòng)作里andThen了4個(gè)動(dòng)作,這一條動(dòng)作鏈作為一個(gè)整體傳入了Setorders,因此只會(huì)有一條日志信息輸出。
- 至于什么時(shí)候使用,根據(jù)實(shí)際情況,例如通知廚房做一道便當(dāng)快餐,而便當(dāng)快餐假設(shè)是由一份牛肉飯+一份啤酒鴨構(gòu)成的,那么在這里這一個(gè)訂單就可以使用這樣的andThen來進(jìn)行構(gòu)造,這樣的鏈?zhǔn)浇M合其實(shí)是有序的,其實(shí)這就是建造者模式的lambda表示形式。
- 使用這樣的方法,你的設(shè)計(jì)里就是包含了命令模式+建造者模式,以lambda形式表現(xiàn)出來,當(dāng)然如果不按照順序的話,這樣的模式你還可以理解成組合的形式。
lambda的方法引用固然使得代碼清晰可見,但是壞處是一旦使用了方法引用,就無法進(jìn)行l(wèi)ambda的鏈?zhǔn)秸{(diào)用,也就是無法使用andThen這樣的鏈?zhǔn)椒椒?,但是我們通過一個(gè)中轉(zhuǎn)站,先將方法引用轉(zhuǎn)換成函數(shù)接口,再鏈?zhǔn)秸{(diào)用,因?yàn)榉椒ㄒ帽举|(zhì)上是lambda的語法糖,下面是轉(zhuǎn)換方法,十分簡易。
public class FunctionCastUtil {
public static <T> Consumer<T> asConsumer(Consumer<T> consumer) {
return consumer;
}
}
客戶端代碼
import static com.lambda.functionutils.FunctionCastUtil.*;
public class Client {
public static void main(String[] args) {
//準(zhǔn)備廚房,服務(wù)員,菜單命令工作
Kitchen kitchen = new Kitchen();
Waiter waiter = new Waiter();
//開始營業(yè)
System.out.println("=======================添加訂單環(huán)節(jié)=======================");
// 顧客:服務(wù)員 一份牛肉飯!
// 顧客:服務(wù)員 一份啤酒鴨!
// 顧客:服務(wù)員 一份西紅柿炒雞蛋!
// 顧客:服務(wù)員 兩份啤酒鴨!
// 顧客:服務(wù)員 兩份啤酒鴨!
//這里使用anThen完成鏈?zhǔn)秸{(diào)用,注意這里是只有一條訂單,所以要注意使用的場合
waiter.setOrders(asConsumer(Kitchen::beefRice)
.andThen(Kitchen::beerDuck)
.andThen(Kitchen::scrambledEggsWithTomatoes)
.andThen(Kitchen::beerDuck)
.andThen(Kitchen::beerDuck));
System.out.println("==========服務(wù)員將訂單送至廚房,廚房按照訂單順序開始做飯=========");
//服務(wù)員通知廚房按照訂單順序開始做
waiter.notifyKitchen(kitchen);
}
}
因?yàn)檩敵鼋Y(jié)果不同,所以這里貼一下輸出結(jié)果,根據(jù)輸出結(jié)果就明白這里的訂單只記錄了一條,這一條里面包含了要做多種菜,在廚房那里實(shí)際完成的結(jié)果是一樣的,大家可以根據(jù)不同的需要使用不同的鏈?zhǔn)絻?yōu)化
=======================添加訂單環(huán)節(jié)=======================
添加訂單成功! 訂單時(shí)間: 2017-10-24T07:01:59.444
==========服務(wù)員將訂單送至廚房,廚房按照訂單順序開始做飯=========
一份牛肉飯做好了!
一份啤酒鴨做完啦!
一份西紅柿炒雞蛋做好了!
一份啤酒鴨做完啦!
一份啤酒鴨做完啦!
Process finished with exit code 0
結(jié)尾
使用lambda優(yōu)化之后的命令模式在保證優(yōu)點(diǎn)的同時(shí)極大的減少了代碼量,簡直完美。這就是語言特性所帶來的力量,簡單的幾處修改就能獲得如此多的受益,這也是我為什么這么喜歡lambda的原因。
歡迎加入學(xué)習(xí)交流群569772982,大家一起學(xué)習(xí)交流。