状态机匹配(一)(从零实现)

之所以是要做状态机,是因为最近工作上的业务成分实在侵入性太多,代码可以搞定,但是不易维护,更不够优雅。比如
一个搜索框,对应四个选项卡类型搜索,牵扯到4个或更多的服务调用,拿到数据还需去重聚合,提取想要的数据后,还需要本地数据库匹配组装,等等。

比如这是控制器,我需要分发出去,部分外部服务还不支持分页

 /*
    顾客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());
    }

比如从第二个选项卡筛选顾客关联的一些信息

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());
   }

业务无错,只是架构规划,导致实现起来非常不流畅

所以这俩天产生了关于状态机的想法,并且初步实现了

需要提供具体的mapFunc容器,为了优雅的使用,我也做了一个提取注解的mapFunc,使用者也可以使用如下的实例,就可以产生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("测试终点状态").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.他是一个抽象类(为了实现生命周期,参考andorid的Activity的实现)


图片.png

简单介绍下生命周期

/*
        组件创建
        提供初始化对象
         */
    public abstract T onCreate();

    /*
    提供属性加载
    提供容器加载
    设置结束节点
     */
    public abstract Pair<Resource,T> onStart();


    /*
    优先使用注解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就很灵活实现了)

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

第一个表示继承状态节点的类型,第二个Resource表示资源的类型,第三个是存储mapFunc的容器,第四个是响应,第五个是异常类型
c.状态的组装及循环执行
这个也属于生命周期内部方法,使用可选择性覆盖,也可默认执行
考虑到状态的更替,所以使用状态体的循环知道发生异常或执行到终止状态

/*
    组件执行
     */
    public   T onProcess(Container functionContainer){
        T responseNode=null;
        //结束flag
        boolean flag = false;
        while (!flag){
            val loopState = iTranslateState.translate(this);

            flag = loopState.end();

            //到达最后一个状态节点
            //或者发生异常了的情况,终止
            if (flag){
                //结束所有节点循环
                log.info("状态循环机制结束");
                //执行最后的终点节点状态
                log.info("执行最后的终点节点状态");
                responseNode =  iTranslateState.translate(this).getStateCode();

            }
        }
        return responseNode;
    }

节点间链表式的引用,并且处理传递捕获到的异常,节点续传


  @Override
    public StateComponent<T,Resource,Container,Response,Ex>
    translate(
            StateComponent<T,Resource,Container,Response,Ex> stateComponent
            )
    {
        //判断当前是否最后一个节点
        //Boolean endFlag = stateComponent.getStateCode().end(stateComponent.getEndStateCode());
        /*
        最后一个节点可以设置响应,灵活配置
         */
        //if (endFlag) stateNode.setResponse(ResultDTO.builder().message("最后一个节点可以设置响应").build());
        val current = stateComponent.getStateCode();
        val next = iComboMapRouterProcessFunc.routerFunc(
                current,
                stateComponent.getContainer(),
                current
        );

        if (next.getEx()!=null){
            stateComponent.setStateCode(stateComponent.getEndStateCode());
            //组装异常到最后节点
            stateComponent.getEndStateCode().setEx(next.getEx());
            return stateComponent;
        }

        current.setAfterState(next);
        //否则返回新节点,并设置上一节点,前后关联
        next.setBeforeState(current);
        stateComponent.setStateCode(
                (T) next
        );



        return stateComponent;
    }

d.注解式的mapFunc定义
这里我在状态3做了一个测试异常,以便于做状态处理及传递的异常处理,
异常处理,分了俩处捕获
a.mapFunc容器里接口类型对接反射的函数引用的异常捕获并设置到节点内部去

//函数转调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节点本身的定义也做了异常的处理机制
他实现了IStateException这个异常代理,所以在状态机的执行就可以很方便的补货异常信息,至于a的情况,是因为反射本身的异常比较特殊,所以单独处理,这样便于区分异常情况(业务导致还是代码本身结构异常)

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

以下就是一个简单的状态的循环的机遇注解的例子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("测试终点状态").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();
    }
}

结尾:
希望有时间写到状态机匹配(二)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容