狀態(tài)機匹配(一)(從零實現(xiàn))

之所以是要做狀態(tài)機痊乾,是因為最近工作上的業(yè)務成分實在侵入性太多施逾,代碼可以搞定碧磅,但是不易維護旗闽,更不夠優(yōu)雅。比如
一個搜索框蜡塌,對應四個選項卡類型搜索碉纳,牽扯到4個或更多的服務調(diào)用,拿到數(shù)據(jù)還需去重聚合馏艾,提取想要的數(shù)據(jù)后劳曹,還需要本地數(shù)據(jù)庫匹配組裝,等等琅摩。

比如這是控制器铁孵,我需要分發(fā)出去,部分外部服務還不支持分頁

 /*
    顧客APP查詢接口
     */
    @MethodInfo(code = "CustomerControllerQuery")
    @GetMapping("/query")
    String customersQuery(
            @PageableDefault Pageable pageable,
            Principal principal,
            String keyword,
            @RequestParam(defaultValue = "0") int tabIndex
    ){
        String userName = principal.getName();

        switch (tabIndex){
            case 0:
                return handlePhoneNumber(keyword,userName,pageable);
            case 1:
                return handleGoods(keyword,userName);
            case 2:
                return handlePreStoreOrder(keyword,userName);
            case 3:
                return handleDisposition(keyword,userName,pageable);
        }


        return JSON.toJSONString(ResultDTO.builder()
                .message("操作失敗")
                .isSuccess(false)
                .build());
    }

比如從第二個選項卡篩選顧客關聯(lián)的一些信息

String handlePreStoreOrder(String keyword,String userName){
       val  prestoreOrderReq = IOrderServiceClient
                                       .PrestoreOrderReq
                                       .builder()
                                       .employeeCode(userName)
                                       .orderName(keyword)
                                       .build();
       ResultDTO<List<IOrderServiceClient.PrestoreOrderResp>>
               resultDTO = iOrderServiceClient.findOrderList(
                       iOrderServiceClient.beanToMap(prestoreOrderReq)
                           );
       if (resultDTO.getIsSuccess()){
           val data=resultDTO.getData().stream()
                   .filter(c -> c.getCustomerId() != null).collect(Collectors.toList());
           Set<IOrderServiceClient.PrestoreOrderResp> customerSet=
                   new TreeSet<>((o1, o2) -> o1.getCustomerId().compareTo(o2.getCustomerId()));
           customerSet.addAll(data);
           if (customerSet != null && customerSet.size() > 0) {
               val customerTuples=customerSet.parallelStream()
                       .map(
                               c -> {
                                   List tasks=taskRepository.findAllByUserNameAndCustomerId(
                                           1,
                                           userName, c.getCustomerId()
                                   );
                                   return CustomerTuple.builder()
                                           .id(c.getCustomerId())
                                           .customerName(c.getCustomerName())
                                           .customerMobile(c.getCustomerPhone())
                                           .existTask((tasks == null || tasks.size() == 0) ? false : true)
                                           .tasks(
                                                   tasks
                                           )
                                           .build();
                               }

                       ).collect(Collectors.toList());

               return JSON.toJSONString(ResultDTO.builder()
                       .data(buildPageInfo(customerTuples))
                       .build(), new SimplePropertyPreFilter() {
                   {
                       getExcludes().add("taskDetails");
                   }
               }, WriteMapNullValue);
           }
       }
       return JSON.toJSONString(ResultDTO.builder().isSuccess(resultDTO.getIsSuccess()).data(buildPageInfo(resultDTO.getData())).build());
   }

業(yè)務無錯房资,只是架構規(guī)劃蜕劝,導致實現(xiàn)起來非常不流暢

所以這倆天產(chǎn)生了關于狀態(tài)機的想法,并且初步實現(xiàn)了

需要提供具體的mapFunc容器,為了優(yōu)雅的使用轰异,我也做了一個提取注解的mapFunc,使用者也可以使用如下的實例熙宇,就可以產(chǎn)生mapFunc容器

@Slf4j
@Builder
public class DemoFuncMap implements IMapFunction {

    @MapFunctionListener(name = "測試01",group = "t",code = "test01")
    public StateNode test01(StateNode<Map,ResultDTO> currentNode){
        log.info("test01");
        return StateNode.builder().nodeCode("t_test03").build();
    }

    @MapFunctionListener(name = "測試02",group = "t",code = "test02")
    public StateNode test02(StateNode currentNode){
        log.info("test02");
        return StateNode.builder().response(ResultDTO.builder().message("測試終點狀態(tài)").build()).build();
    }

    @MapFunctionListener(name = "測試03",group = "t",code = "test03")
    public StateNode test03(StateNode currentNode){
        log.info("test03");
        //測試異常
        //int ss = 1/0;
        return StateNode.builder().nodeCode("t_test02").build();
    }
}

這是state對應的Func
當然如果你想更靈活或者豐富使用可以傳遞一個

@Slf4j
//@Component
@Data
@Accessors(chain = true)
public abstract class StateComponent
        <T extends StateNode,Resource,
        Container extends Map<T,IMapRouterProcessStateNodeFunc<T>>,
                Response extends ResultDTO,Ex extends Exception> {

Container自組類型就是mapFunc的映射容器
下面我來幾本介紹下StateComponent這個組件吧
一.StateComponent結構
a.他是一個抽象類(為了實現(xiàn)生命周期,參考andorid的Activity的實現(xiàn))


圖片.png

簡單介紹下生命周期

/*
        組件創(chuàng)建
        提供初始化對象
         */
    public abstract T onCreate();

    /*
    提供屬性加載
    提供容器加載
    設置結束節(jié)點
     */
    public abstract Pair<Resource,T> onStart();


    /*
    優(yōu)先使用注解class的mapFunction
     */
    public Container onLoadContainer(IMapFunction<T> iMapFunction,Container injectContainer){
        Container container = injectContainer;
        if (this.iMapFunctionListenerProcess!=null){
            container = (Container) this.iMapFunctionListenerProcess.handler(iMapFunction);
        }
        return container;
    }

b.別名類型 比較不太友好的是java不支持靈活的自組別名類型(swift kotlin就很靈活實現(xiàn)了)

        <T extends StateNode,Resource,
        Container extends Map<T,IMapRouterProcessStateNodeFunc<T>>,
                Response extends ResultDTO,Ex extends Exception>

第一個表示繼承狀態(tài)節(jié)點的類型溉浙,第二個Resource表示資源的類型,第三個是存儲mapFunc的容器蒋荚,第四個是響應戳稽,第五個是異常類型
c.狀態(tài)的組裝及循環(huán)執(zhí)行
這個也屬于生命周期內(nèi)部方法,使用可選擇性覆蓋期升,也可默認執(zhí)行
考慮到狀態(tài)的更替惊奇,所以使用狀態(tài)體的循環(huán)知道發(fā)生異常或執(zhí)行到終止狀態(tài)

/*
    組件執(zhí)行
     */
    public   T onProcess(Container functionContainer){
        T responseNode=null;
        //結束flag
        boolean flag = false;
        while (!flag){
            val loopState = iTranslateState.translate(this);

            flag = loopState.end();

            //到達最后一個狀態(tài)節(jié)點
            //或者發(fā)生異常了的情況播赁,終止
            if (flag){
                //結束所有節(jié)點循環(huán)
                log.info("狀態(tài)循環(huán)機制結束");
                //執(zhí)行最后的終點節(jié)點狀態(tài)
                log.info("執(zhí)行最后的終點節(jié)點狀態(tài)");
                responseNode =  iTranslateState.translate(this).getStateCode();

            }
        }
        return responseNode;
    }

節(jié)點間鏈表式的引用颂郎,并且處理傳遞捕獲到的異常,節(jié)點續(xù)傳


  @Override
    public StateComponent<T,Resource,Container,Response,Ex>
    translate(
            StateComponent<T,Resource,Container,Response,Ex> stateComponent
            )
    {
        //判斷當前是否最后一個節(jié)點
        //Boolean endFlag = stateComponent.getStateCode().end(stateComponent.getEndStateCode());
        /*
        最后一個節(jié)點可以設置響應,靈活配置
         */
        //if (endFlag) stateNode.setResponse(ResultDTO.builder().message("最后一個節(jié)點可以設置響應").build());
        val current = stateComponent.getStateCode();
        val next = iComboMapRouterProcessFunc.routerFunc(
                current,
                stateComponent.getContainer(),
                current
        );

        if (next.getEx()!=null){
            stateComponent.setStateCode(stateComponent.getEndStateCode());
            //組裝異常到最后節(jié)點
            stateComponent.getEndStateCode().setEx(next.getEx());
            return stateComponent;
        }

        current.setAfterState(next);
        //否則返回新節(jié)點,并設置上一節(jié)點,前后關聯(lián)
        next.setBeforeState(current);
        stateComponent.setStateCode(
                (T) next
        );



        return stateComponent;
    }

d.注解式的mapFunc定義
這里我在狀態(tài)3做了一個測試異常容为,以便于做狀態(tài)處理及傳遞的異常處理乓序,
異常處理,分了倆處捕獲
a.mapFunc容器里接口類型對接反射的函數(shù)引用的異常捕獲并設置到節(jié)點內(nèi)部去

//函數(shù)轉調(diào)method坎背,帶同代理注解方法
                            IMapRouterProcessStateNodeFunc<StateNode>
                                    iMapRouterProcessStateNodeFunc =
                                    currentNode -> {
                                        StateNode newStateNode = null;
                                        try {
                                            //newStateNode = (StateNode) method.invoke(clazz,currentNode);
                                            newStateNode = (StateNode) method.invoke(existMapFunction,currentNode);
                                        } catch (IllegalAccessException e) {
                                            e.printStackTrace();
                                            currentNode.setEx(e);
                                        } catch (InvocationTargetException e) {
                                            e.printStackTrace();
                                            currentNode.setEx(e);
                                        }finally {
                                            if (newStateNode==null){
                                                newStateNode = currentNode;

                                            }
                                        }
                                        return newStateNode;
                                    };

b.除了a所術替劈,StateNode節(jié)點本身的定義也做了異常的處理機制
他實現(xiàn)了IStateException這個異常代理,所以在狀態(tài)機的執(zhí)行就可以很方便的補貨異常信息得滤,至于a的情況陨献,是因為反射本身的異常比較特殊,所以單獨處理懂更,這樣便于區(qū)分異常情況(業(yè)務導致還是代碼本身結構異常)

public class StateNode<RequestTuple,Response extends ResultDTO> implements IExactFilter<StateNode>,IStateEndFace<StateNode>,IStateNodeFlow,IStateException

以下就是一個簡單的狀態(tài)的循環(huán)的機遇注解的例子demo

@Slf4j
@Builder
public class DemoFuncMap implements IMapFunction {

    @MapFunctionListener(name = "測試01",group = "t",code = "test01")
    public StateNode test01(StateNode<Map,ResultDTO> currentNode){
        log.info("test01");
        return StateNode.builder().nodeCode("t_test03").build();
    }

    @MapFunctionListener(name = "測試02",group = "t",code = "test02")
    public StateNode test02(StateNode currentNode){
        log.info("test02");
        return StateNode.builder().response(ResultDTO.builder().message("測試終點狀態(tài)").build()).build();
    }

    @MapFunctionListener(name = "測試03",group = "t",code = "test03")
    public StateNode test03(StateNode currentNode){
        log.info("test03");
        //測試異常
        int ss = 1/0;
        return StateNode.builder().nodeCode("t_test02").build();
    }
}

結尾:
希望有時間寫到狀態(tài)機匹配(二)

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眨业,一起剝皮案震驚了整個濱河市急膀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌龄捡,老刑警劉巖卓嫂,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異墅茉,居然都是意外死亡命黔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門就斤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悍募,“玉大人,你說我怎么就攤上這事洋机∽寡纾” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵绷旗,是天一觀的道長喜鼓。 經(jīng)常有香客問我,道長衔肢,這世上最難降的妖魔是什么庄岖? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮角骤,結果婚禮上隅忿,老公的妹妹穿的比我還像新娘。我一直安慰自己邦尊,他們只是感情好背桐,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝉揍,像睡著了一般链峭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上又沾,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天弊仪,我揣著相機與錄音,去河邊找鬼杖刷。 笑死撼短,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的挺勿。 我是一名探鬼主播曲横,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了禾嫉?” 一聲冷哼從身側響起灾杰,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熙参,沒想到半個月后艳吠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡孽椰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年昭娩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黍匾。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡栏渺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锐涯,到底是詐尸還是另有隱情磕诊,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布纹腌,位于F島的核電站霎终,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏升薯。R本人自食惡果不足惜莱褒,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涎劈。 院中可真熱鬧保礼,春花似錦、人聲如沸责语。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坤候。三九已至,卻和暖如春企蹭,著一層夾襖步出監(jiān)牢的瞬間白筹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工谅摄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留徒河,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓送漠,卻偏偏與公主長得像顽照,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容