Java命令模式以及來自lambda的優(yōu)化(二)

使用函數(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í)交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容