• 设计模式的优雅:函数式pipeline+策略模式实现复杂业务@落雨


    现在有一个新需求,要求对老接口进行升级,原有逻辑基础上做功能路由,识别老业务走老接口,命中新业务(灰度)则走新接口,且新老接口出入参焕然一新,完全不同,但是要保证原有接口出入参一致(相当于强行换轮子还不要影响线上业务,前端都无需改动)。你会怎么设计?本篇文章提供2种方式来解决

    流程图:

    一、常规做法(最简单的玩法)

    增加路由处理类,分别路由到原有接口和新接口,由各自接口进行处理

    if(老逻辑){
      doSomeThingOld(context);
    } else {
      doSomeThingNew(context);    
    }
    

    二、优雅做法(抽象、piple、策略)

    如果按照常规模式,那么这个需求就结束了,只需在入口层增加一个路由,后面各自的接口自己去实现入参校验、参数组装、逻辑处理、结果变换、log打印,但是你稍加思索就觉得,哎呀,这一套组合拳下来,岂不是一样的流程,很好,我们开始抽象。

    2.1 抽出函数处理器,负责执行函数

    /**
    * Pipe执行器,使用Stream.map处理各层Fuction
    *  <pre>
         Function<ItemResultContext, ItemResultContext>> 的含义是使用一个ItemResultContext入参,传出一个ItemResultContext出参
       </pre>
    * @param context
    * @param functionList
    * @return
    */
    private Optional<ItemResultContext> buildItemResult(ItemResultContext context,
       List<Function<ItemResultContext, ItemResultContext>> functionList) {
       // 获取context流,context是每个函数的出入参
       Stream<ItemResultContext> stream = Stream.of(context);
       if (CollectionUtils.isEmpty(functionList)) {
           return Optional.empty();
       }
    
       // 同步执行,循环处理函数流, stream1 -> stream2 -> stream3 -> ... streamN
       for (Function f : functionList) {
           // @link{<R> Stream<R> map(Function<? super T, ? extends R> mapper);}
           stream = stream.map(f);
       }
    
       return stream.findFirst();
    }
    

    2.2 抽出pipeline函数处理器,用来定义流程

    /**
    * pipe流程(pipe组装,交给子类实现)
    * @param context
    * @param functionList
    * @return
    */
    public ItemResultContext buildItemResult(ItemResultContext context) {
            // 组装流程
            List<Function<ItemResultContext, ItemResultContext>> list = Lists.newArrayList();
            list.add(logAroundBuildItemResult("covertReq", this::covertReq));
            list.add(logAroundBuildItemResult("queryItems", this::queryItems));
            list.add(logAroundBuildItemResult("covertItems", this::covertItems));
            list.add(logAroundBuildItemResult("queryCategorys", this::queryCategorys));
            list.add(logAroundBuildItemResult("combineResult", this::combineResult));
            Optional<ItemResultContext> opt = buildItemResult(context, list);
            return opt.orElse(context);
        }
    

    2.3 抽出log处理,环绕切面,切函数

    /**
    * Pipe环绕切面,apply -> function
    * @param desc
    * @param func
    * @return
    */
    private Function<ItemResultContext, ItemResultContext> logAroundBuildItemResult(String desc,
       Function<ItemResultContext, ItemResultContext> func) {
       return req -> {
           long start = System.currentTimeMillis();
           log.error("[{}] start", desc);
           ItemResultContext resp = func.apply(req);
           log.error("[{}] finish, time elapsed {}ms", desc, System.currentTimeMillis() - start);
           return resp;
       };
    }
    

    2.4 将此函数处理器、切面环绕等基础方法抽到父类,提取抽象方法,让子类来实现函数pipe组装,让子类来实现更多的业务场景

    @Slf4j
    public abstract class AbstractItemManager implements ItemManager {
    
        /**
         * 获取结果(pipe组装,交给子类实现)
         * @param context
         * @return
         */
        public abstract ItemResultContext buildItemResult(ItemResultContext context);
    
        /**
         * 转换req到上下文
         * @param context
         * @return
         */
        public abstract ItemResultContext covertReq(ItemResultContext context);
    
        /**
         * 查询商品
         * @param context
         * @return
         */
        public abstract ItemResultContext queryItems(ItemResultContext context);
    
        /**
         * 转换结果
         * @param context
         * @return
         */
        public abstract ItemResultContext covertItems(ItemResultContext context);
    
        /**
         * 查询类目
         * @param context
         * @return
         */
        public abstract ItemResultContext queryCategorys(ItemResultContext context);
    
    /**
         * Pipe执行器
         * @param context
         * @param functionList
         * @return
         */
        private Optional<ItemResultContext> buildItemResult(ItemResultContext context,
            List<Function<ItemResultContext, ItemResultContext>> functionList) {
            // 获取context
            Stream<ItemResultContext> stream = Stream.of(context);
            if (CollectionUtils.isEmpty(functionList)) {
                return Optional.empty();
            }
    
            // 函数列表执行,同步执行
            for (Function f : functionList) {
                stream = stream.map(f);
            }
    
            return stream.findFirst();
        }
    
        /**
         * Pipe环绕切面,apply -> function
         * @param desc
         * @param func
         * @return
         */
        private Function<ItemResultContext, ItemResultContext> logAroundBuildItemResult(String desc,
            Function<ItemResultContext, ItemResultContext> func) {
            return req -> {
                long start = System.currentTimeMillis();
                log.error("[{}] start", desc);
                ItemResultContext resp = func.apply(req);
                log.error("[{}] finish, time elapsed {}ms", desc, System.currentTimeMillis() - start);
                return resp;
            };
        }
    }
    

    2.5 完事,很清爽.

    有兄弟问我,ItemResultContext是干啥的,这个里面就是个上下文对象,里面可以放Anything,比如入参、出参对象等,方便Function执行的时候(Function<ItemResultContext/**入参类型T**/, ItemResultContext>/**出参类型T**/),上下传递,是统一的最外层容器。

    @Data
    public class ItemResultContext implements Serializable {
    
        private static final long serialVersionUID = -1L;
    
        /**
         * 老模型上下文
         */
        private ItemContext itemContext;
    
        /**
         * 新模型上下文
         */
        private XItemContext xItemContext;
     
    }
    

    落雨 2021-09-10 20:12:45
    http://www.js-dev.cn

  • 相关阅读:
    [论文笔记]CVPR2017_Joint Detection and Identification Feature Learning for Person Search
    [论文笔记]Objects as Points
    [论文笔记]ICCV2017_SVDNet for Pedestrian Retrieval
    [论文笔记]ICPR2016_Person Re-Identification Using CNN Features Learned from Combination of Attributes
    VMware ESXI6.0服务器安装系列:RAID设置
    LVM基础详细说明及动态扩容lvm逻辑卷的操作记录
    调用对象 “ha-datastoresystem”的“HostDatastoreSystem.QueryVmfsDatastoreCreateOptions” 失败。
    动态扩容lvm逻辑卷的操作记录
    Kubernetes之Flannel介绍
    Linux服务器同步网络时间
  • 原文地址:https://www.cnblogs.com/ae6623/p/15252933.html
Copyright © 2020-2023  润新知