1. 還不懂 Consumer 接口?那你就OUT了

        共 2761字,需瀏覽 6分鐘

         ·

        2021-12-01 05:22

        背景

        沒(méi)錯(cuò),我還在做 XXXX 項(xiàng)目,還在與第三方對(duì)接接口,不同的是這次是對(duì)自己業(yè)務(wù)邏輯的處理。

        在開(kāi)發(fā)過(guò)程中我遇到這么一個(gè)問(wèn)題:

        表結(jié)構(gòu):一張主表A ,一張關(guān)聯(lián)表B ,表 A 中存儲(chǔ)著表 B 記錄的狀態(tài)。

        場(chǎng)景:第一步創(chuàng)建主表數(shù)據(jù),插入A表;第二步調(diào)用第三方接口插入B表同時(shí)更新A表的狀態(tài)。此時(shí)大家應(yīng)該都會(huì)想到在進(jìn)行第二步的時(shí)候需要做好數(shù)據(jù)的冪等性。這樣的話(huà)就會(huì)存在以下幾種情況:

        一、B表中不存在與A表關(guān)聯(lián)的數(shù)據(jù),此時(shí)需要調(diào)用第三方接口,插入B表同時(shí)更新A表的狀態(tài);

        二、B表中存在與A表關(guān)聯(lián)的數(shù)據(jù);

        1. A表中的狀態(tài)為處理中:直接返回處理中字樣;
        2. A表中的狀態(tài)為處理成功:直接返回成功的字樣;
        3. A表中的狀態(tài)為處理失?。捍藭r(shí)需要調(diào)用第三方接口,更新B表同時(shí)更新A表的狀態(tài);

        代碼實(shí)現(xiàn)

        首先我是這樣編寫(xiě)的偽代碼

        B?b?=?this.baseMapper.selectOne(queryWrapper);
        if?(b?!=?null)?{
        ?String?status?=?b.getStatus();
        ?if?(Objects.equals(Constants.STATUS_ING,?status)){
        ??return?"處理中";
        ?}?else?if?(Objects.equals(Constants.STATUS_SUCCESS,?status)){
        ??return?"處理成功";
        ?}
        ?//失敗的操作
        ?//請(qǐng)求第三方接口并解析響應(yīng)結(jié)果
        ?......
        ?if?(ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode()))?{
        ????????......
        ????????//更新B表操作
        ??bb.setStatus(Constants.STATUS_ING);
        ??mapper.updateById(bb);

        ??//更新A表的狀態(tài)
        ??a.setStatus(Constants.STATUS_ING);
        ??aMapper.updateById(a);
        ?}
        ?
        }?else?{
        ?//請(qǐng)求第三方接口并解析響應(yīng)結(jié)果
        ?......
        ?if?(ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode()))?{
        ????????......
        ????????//插入B表操作
        ??bb.setStatus(Constants.STATUS_ING);
        ??mapper.insert(bb);

        ??//更新A表的狀態(tài)
        ??a.setStatus(Constants.STATUS_ING);
        ??aMapper.updateById(a);
        ?}
        }

        不知道細(xì)心的小伙伴是否發(fā)現(xiàn),存在B表記錄并且狀態(tài)為“失敗”的情況和不存在B表的情況除了插入B表或者更新B表的操作之外,其余的操作都是相同的。

        如果我們想要將公共的部分抽取出來(lái),發(fā)現(xiàn)都比較零散,還不如不抽取,但是不抽取代碼又存在大量重復(fù)的代碼不符合我的風(fēng)格。于是我便將手伸向了 Consumer 接口。

        更改之后的偽代碼

        B?b?=?this.baseMapper.selectOne(queryWrapper);
        if?(b?!=?null)?{
        ?String?status?=?b.getStatus();
        ?if?(Objects.equals(Constants.STATUS_ING,?status)){
        ??return?"處理中";
        ?}?else?if?(Objects.equals(Constants.STATUS_SUCCESS,?status)){
        ??return?"處理成功";
        ?}
        ?//失敗的操作
        ?getResponse(dto,?response,?s?->?mapper.updateById(s));
        }?else?{
        ?getResponse(dto,?response,?s?->?mapper.updateById(s));
        }

        public?void?getResponse(DTO?dto,?Response?response,?Consumer?consumer){
        ?//請(qǐng)求第三方接口并解析響應(yīng)結(jié)果
        ?......
        ?if?(ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode()))?{
        ????????......
        ??bb.setStatus(Constants.STATUS_ING);
        ?
        ??consumer.accept(bb);

        ??//更新A表的狀態(tài)
        ??a.setStatus(Constants.STATUS_ING);
        ??aMapper.updateById(a);
        ?}
        }

        看到這,如果大家都已經(jīng)看懂了,那么恭喜你,說(shuō)明你對(duì) Consumer 的使用已經(jīng)全部掌握了。如果你還存在一絲絲的疑慮,那么就接著往下看,我們將介紹一下四種常見(jiàn)的函數(shù)式接口。

        函數(shù)式接口

        那什么是函數(shù)式接口呢?函數(shù)式接口是只有一個(gè)抽象方法(Object的方法除外),但是可以有多個(gè)非抽象方法的接口,它表達(dá)的是一種邏輯上的單一功能。

        @FunctionalInterface

        @FunctionalInterface ?注解用來(lái)表示該接口是函數(shù)式接口。它有助于及早發(fā)現(xiàn)函數(shù)式接口中出現(xiàn)的或接口繼承的不適當(dāng)?shù)姆椒暶鳌?/p>

        如果接口用該注解來(lái)注釋?zhuān)珜?shí)際上不是函數(shù)式接口,則會(huì)在編譯時(shí)報(bào)錯(cuò)。

        Consumer

        我們一般稱(chēng)之為“消費(fèi)者”,它表示接受單個(gè)輸入?yún)?shù)但不返回結(jié)果的操作。不同于其它函數(shù)式接口,Consumer 預(yù)期通過(guò)副作用進(jìn)行操作。

        那什么又是副作用呢?說(shuō)一下我所理解的副作用,副作用其實(shí)就是一個(gè)函數(shù)是否會(huì)修改它范圍之外的資源,如果有就叫有副作用,反之為沒(méi)有副作用。比如修改全局變量,修改輸入?yún)?shù)所引用的對(duì)象等。

        @FunctionalInterface
        public?interface?Consumer<T>?{

        ????/**
        ?????*??對(duì)給定的參數(shù)執(zhí)行此操作。
        ?????*/

        ????void?accept(T?t);

        ????/**
        ?????*?
        ?????*??返回一個(gè)組合的 Consumer ,依次執(zhí)行此操作,然后執(zhí)行after操作。
        ?????*??如果執(zhí)行任一操作會(huì)拋出異常,它將被轉(zhuǎn)發(fā)到組合操作的調(diào)用者。
        ?????*??如果執(zhí)行此操作會(huì)引發(fā)異常,則不會(huì)執(zhí)行after操作。
        ?????*/

        ????default?Consumer?andThen(Consumersuper?T>?after)?{
        ????????Objects.requireNonNull(after);
        ????????return?(T?t)?->?{?accept(t);?after.accept(t);?};
        ????}
        }

        正如我們案例中遇到的場(chǎng)景,我們只需要將要執(zhí)行的邏輯方法當(dāng)作參數(shù)傳入 getResponse() 中,然后在該方法中執(zhí)行 accept() 方法進(jìn)行消費(fèi)即可。如果還不理解,我們可以把它轉(zhuǎn)換為匿名內(nèi)部類(lèi)的調(diào)用方式。

        ?getResponse(dto,?response,?new?Consumer()?{
        ????@Override
        ????public?void?accept(B?bb)?{
        ??????mapper.insert(bb);
        ????}
        });

        當(dāng)調(diào)用accept() 方法的時(shí)候就會(huì)去調(diào)用匿名內(nèi)部類(lèi)的方法了,也就是我們傳入 getResponse() 的邏輯方法。

        Supplier

        我們一般稱(chēng)之為“生產(chǎn)者”,沒(méi)有參數(shù)輸入,但是能返回結(jié)果,為結(jié)果的提供者。

        @FunctionalInterface
        public?interface?Supplier<T>?{

        ????/**
        ?????*??獲取一個(gè)結(jié)果
        ?????*/

        ????T?get();
        }

        可以舉個(gè)簡(jiǎn)單的例子感受下:

        Optional?optional?=?Optional.empty();
        optional.orElseGet(()->Math.random()?);

        //orElseGet?方法的源碼,里邊用到了?get?方法
        public?T?orElseGet(Supplier?other)?{???
        ????return?value?!=?null???value?:?other.get();
        }

        Function

        我把它稱(chēng)為“轉(zhuǎn)換者”,表示接收一個(gè)參數(shù)通過(guò)處理之后返回一個(gè)結(jié)果的函數(shù)。

        @FunctionalInterface
        public?interface?Function<T,?R>?{

        ????/**
        ?????*??將?T?類(lèi)型的參數(shù)傳入,經(jīng)過(guò)函數(shù)表達(dá)式的計(jì)算,返回?R?類(lèi)型的結(jié)果
        ?????*/

        ????R?apply(T?t);

        ????/**
        ?????*?返回一個(gè)組合函數(shù),先將參數(shù)應(yīng)用于 before 函數(shù),然后將結(jié)果應(yīng)用于當(dāng)前函數(shù),返回最終結(jié)果。
        ?????*?如果對(duì)任一函數(shù)的求值引發(fā)異常,則會(huì)將其轉(zhuǎn)發(fā)給組合函數(shù)的調(diào)用方。
        ?????*/

        ????default??Function?compose(Functionsuper?V,???extends?T>?before)?{
        ????????Objects.requireNonNull(before);
        ????????return?(V?v)?->?apply(before.apply(v));
        ????}

        ????/**
        ?????*?返回一個(gè)組合函數(shù),先將參數(shù)應(yīng)用與當(dāng)前函數(shù),然后將結(jié)果應(yīng)用于 after 函數(shù),返回最終的結(jié)果。
        ?????*?如果對(duì)任一函數(shù)的求值引發(fā)異常,則會(huì)將其轉(zhuǎn)發(fā)給組合函數(shù)的調(diào)用方。?
        ?????*/

        ????default??Function?andThen(Functionsuper?R,???extends?V>?after)?{
        ????????Objects.requireNonNull(after);
        ????????return?(T?t)?->?after.apply(apply(t));
        ????}

        ????/**
        ?????*??返回始終返回其輸入?yún)?shù)的函數(shù)。
        ?????*/

        ????static??Function?identity()?{
        ????????return?t?->?t;
        ????}
        }

        我們?cè)?lambda 表達(dá)式中應(yīng)用比較多,所以我們來(lái)簡(jiǎn)單演示下:

        @Data
        @AllArgsConstructor
        public?class?Teacher?{
        ????private?String?name;
        ????private?int?age;
        }

        public?class?TeacherTest?{
        ????public?static?void?main(String[]?args)?{
        ???????List?list?=?Arrays.asList(
        ????????????new?Teacher("張三",25),
        ????????????new?Teacher("李四",28),
        ????????????new?Teacher("王五",18));
        ??????List?collect?=?list.stream().map(item?->?item.getName()).collect(Collectors.toList());
        ??????System.out.println(collect);
        ????}
        }

        其中 map 接收的參數(shù)就是 Function 類(lèi)型, item 為傳入?yún)?shù),item.getName() ?為返回處理的結(jié)果,最后輸出結(jié)果為

        [張三,?李四,?王五]

        Predicate

        我們稱(chēng)之為“判斷者”,通過(guò)接收參數(shù) T 來(lái)返回 boolean 的結(jié)果。

        @FunctionalInterface
        public?interface?Predicate<T>?{

        ????/**
        ?????*??接收一個(gè)參數(shù),?判斷這個(gè)參數(shù)是否匹配某種規(guī)則,?匹配成功返回true,?匹配失敗則返回false
        ?????*/

        ????boolean?test(T?t);

        ????/**
        ?????*??接收一個(gè)?Predicate?類(lèi)型的參數(shù),用當(dāng)前函數(shù)和?other?函數(shù)邏輯與判斷參數(shù)?t?是否匹配規(guī)則,成功返回true,失敗返回?false?
        ?????*??如果當(dāng)前函數(shù)返回?false,則?other?函數(shù)不進(jìn)行計(jì)算
        ?????*?在評(píng)估?Predicate?期間引發(fā)的任何異常都會(huì)轉(zhuǎn)發(fā)給調(diào)用方
        ?????*/

        ????default?Predicate?and(Predicatesuper?T>?other)?{
        ????????Objects.requireNonNull(other);
        ????????return?(t)?->?test(t)?&&?other.test(t);
        ????}

        ????/**
        ?????*??返回當(dāng)前Predicate取反操作之后的Predicate
        ?????*/

        ????default?Predicate?negate()?{
        ????????return?(t)?->?!test(t);
        ????}

        ????/**
        ?????*??接收一個(gè)?Predicate?類(lèi)型的參數(shù),用當(dāng)前函數(shù)和?other?函數(shù)?邏輯或?判斷參數(shù)?t?是否匹配規(guī)則,成功返回true,失敗返回?false?
        ?????*??如果當(dāng)前函數(shù)返回?true,則?other?函數(shù)不進(jìn)行計(jì)算
        ?????*?在評(píng)估?Predicate?期間引發(fā)的任何異常都會(huì)轉(zhuǎn)發(fā)給調(diào)用方
        ?????*/

        ????default?Predicate?or(Predicatesuper?T>?other)?{
        ????????Objects.requireNonNull(other);
        ????????return?(t)?->?test(t)?||?other.test(t);
        ????}

        ????/**
        ?????*??靜態(tài)方法:傳入一個(gè)參數(shù),用來(lái)生成一個(gè) Predicate,調(diào)用test()?方法時(shí)調(diào)的 object -> targetRef.equals(object)?函數(shù)式
        ?????*
        ?????*/

        ????static??Predicate?isEqual(Object?targetRef)?{
        ????????return?(null?==?targetRef)
        ??????????????????Objects::isNull
        ????????????????:?object?->?targetRef.equals(object);
        ????}
        }

        相信大家在編碼過(guò)程中經(jīng)常會(huì)遇到該函數(shù)式接口,我們舉個(gè)例子來(lái)說(shuō)一下:

        public?static?void?main(String[]?args)?{
        ????List?list?=?Arrays.asList(
        ????????new?Teacher("張三",25),
        ????????new?Teacher("李四",28),
        ????????new?Teacher("王五",18));

        ????list?=?list.stream().filter(item?->?item.getAge()>25).collect(Collectors.toList());
        ????list.stream().forEach(item->System.out.println(item.getName()));
        }

        其中 filter() 的參數(shù)為 Predicate 類(lèi)型的,返回結(jié)果為:李四

        看到這兒,我們常見(jiàn)的四種函數(shù)式接口就已經(jīng)介紹完了。說(shuō)實(shí)話(huà),函數(shù)式接口我已經(jīng)看過(guò)好幾遍了,尤其是 ConsumerSupplier。當(dāng)時(shí)只是腦子里學(xué)會(huì)了,沒(méi)有應(yīng)用到具體的項(xiàng)目中,下次再遇到的時(shí)候還是一臉懵逼,不知道大家有沒(méi)有這種感受。

        所以我們需要總結(jié)經(jīng)驗(yàn)教訓(xùn),一定要將代碼的原理搞懂,然后多敲幾遍,爭(zhēng)取應(yīng)用到自己的項(xiàng)目中,提升自己的編碼能力。

        以上就是今天的全部?jī)?nèi)容了,如果你有不同的意見(jiàn)或者更好的idea,歡迎聯(lián)系阿Q,添加阿Q可以加入技術(shù)交流群參與討論呦!



        文章風(fēng)格多變,配圖通俗易懂,故事生動(dòng)有趣。推薦幾篇文章,何不試著讀讀呢?


        推薦閱讀:

        《吃透 MQ 系列》之核心基礎(chǔ)篇

        Redis 宕機(jī),數(shù)據(jù)丟了,老板要辭退我

        程序員必備基礎(chǔ):10種常見(jiàn)安全漏洞淺析

        網(wǎng)關(guān)技術(shù)選型,為什么選擇 Openresty ?

        Redis主節(jié)點(diǎn)的Key已過(guò)期的處理


        關(guān)號(hào)互聯(lián)網(wǎng)全棧架構(gòu)價(jià)

        瀏覽 28
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
            
            

                      • 特黄60分钟免费全过程 | 国产精品永久免费视频 | 娇妻与老头的性系列小说 | 成人97超碰 | 欧美成人一区二区三区电影 |