我写的代码,又被CTO骂了......

作者:微信小助手

发布时间:2020-10-11T14:58:17

大多数时候我都是写一些业务代码,可能一堆 CRUD 就能解决问题,但是这样的工作对技术人的提升并不多,如何让自己从业务中解脱出来找到写代码的乐趣呢,我做过一些尝试,使用设计模式改善自己的业务代码就是其中的一种。



“你这代码写的像坨屎,今天我的代码又被当作典型被 CTO 骂了......于是他给我的建议如下:


责任链设计模式


模式定义


请求在一个链条上处理,链条上的受理者处理完毕之后决定是继续往后传递还是中断当前处理流程。


适用场景


适用于多节点的流程处理,每个节点完成各自负责的部分,节点之间不知道彼此的存在,比如 OA 的审批流,Java Web 开发中的 Filter 机制。


举一个生活中的例子,笔者之前租房的时候遇到了所谓的黑中介,租的时候感觉自己是上帝,但是坏了东西找他修的时候就像个孙子一样。


中介让我找门店客服,门店客服又让我找房东,房东又让我找她家老公,最终好说歹说才把这事了了(租房一定要找正规中介)。


实践经验


笔者目前所做的业务是校园团餐的聚合支付,业务流程很简单:

  • 学生打开手机付款码支付。

  • 食堂大妈使用机具扫付款码收款。


大学食堂有个背景是这样的,食堂有补贴,菜品比较便宜,所以学校是不愿意让社会人士去学校食堂消费的,鉴于此,我们在支付之前加了一套是否允许支付的检验逻辑。


大体如下:

  • 某档口只允许某类用户用户消费,比如教师档口只允许教师消费,学生档口不允许校外用户消费。

  • 某个档口一天只允许某类用户消费几次,比如教师食堂一天只允许学生消费一次。

  • 是否允许非清真学生消费,比如某些清真餐厅,是不允许非清真学生消费的。


针对这几类情况我建立了三类过滤器,分别是:

  • SpecificCardUserConsumeLimitFilter:按用户类型判断是否允许消费。

  • DayConsumeTimesConsumeLimitFilter:按日消费次数判断是否允许消费。

  • MuslimConsumeLimitFilter:非清真用户是否允许消费。


判断逻辑是先通过 SpecificCardUserConsumeLimitFilter 判断当前用户是否可以在此档口消费。


如果允许继续由 DayConsumeTimesConsumeLimitFilter 判断当天消费次数是否已用完;如果未用完继续由 MuslimConsumeLimitFilter 判断当前用户是否满足清真餐厅的就餐条件,前面三条判断,只要有一个不满足就提前返回。


部分代码如下:
public boolean canConsume(String uid,String shopId,String supplierId){
    //获取用户信息,用户信息包含类型(student:学生,teacher:老师,unknown:未知用户)、名族(han:汉族,mg:蒙古族)
    UserInfo userInfo = getUserInfo(uid);
    //获取消费限制信息,限制信息包含是否允许非清真消费、每种类型的用户是否允许消费以及允许消费的次数
   ConsumeConfigInfo consumeConfigInfo = getConsumeConfigInfo(shopId,supplierId) 


    // 构造消费限制过滤器链条
    ConsumeLimitFilterChain filterChain = new ConsumeLimitFilterChain();
    filterChain.addFilter(new SpecificCardUserConsumeLimitFilter());
    filterChain.addFilter(new DayConsumeTimesConsumeLimitFilter());
    filterChain.addFilter(new MuslimConsumeLimitFilter());
    boolean checkResult = filterChain.doFilter(filterChain, schoolMemberInfo, consumeConfigInfo);

    //filterChain.doFilter方法
   public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
             ConsumeConfigInfo consumeConfigInfo )
{
        //迭代调用过滤器
        if(index<filters.size()){
            return filters.get(index++).doFilter(filterChain, userInfo, consumeConfigInfo);
        }
    }

    //SpecificCardUserConsumeLimitFilter.doFilter方法
     public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
             ConsumeConfigInfo consumeConfigInfo )
{
                //获取某一类型的消费限制,比如student允许消费,unknown不允许消费
        CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);

        // 判断当前卡用户是否允许消费
        if (consumeCardConfig != null) {
            if ((!CAN_PAY.equals(cardConsumeConfig .getEnabledPay()))) {
                return false;
            }
        }

                //其余情况,继续往后传递
            return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
        }

    //DayConsumeTimesConsumeLimitFilter.doFilter方法
     public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
             ConsumeConfigInfo consumeConfigInfo )
{
                //获取某一类型的消费限制,比如student可以消费2次
        CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);

                //获取当前用户今天的消费次数
                int consumeCnt = getConsumeCnt(userInfo)        
        if(consumeCnt >= cardConsumeConfig.getDayConsumeTimesLimit()){
                    return false;
                }

                //其余情况,继续往后传递
                return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
        }

总结:将每种限制条件的判断逻辑封装到了具体的 Filter 中,如果某种限制条件的逻辑有修改不会影响其他条件,如果需要新加限制条件只需要重新构造一个 Filter 织入到 FilterChain 上即可。


策略设计模式


模式定义


定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换。


适用场景


主要是为了消除大量的 if else 代码,将每种判断背后的算法逻辑提取到具体的策略对象中,当算法逻辑修改时对使用者无感知,只需要修改策略对象内部逻辑即可。


这类策略对象一般都实现了某个共同的接口,可以达到互换的目的。


实践经验


笔者之前有个需求是用户扫码支付以后向档口的收银设备推送一条支付消息,收银设备收到消息以后会进行语音播报,逻辑很简单,就是调用推送平台推送一条消息给设备即可。


但是由于历史原因,某些设备对接的推送平台是不一样的,A 类设备优先使用信鸽推送,如果失败了需要降级到长轮询机制,B 类设备直接使用自研的推送平台即可。


还有个现状是 A 类和 B 类的消息格式是不一样的(不同的团队开发,后期被整合到一起)。


鉴于此,我抽象出 PushStrategy 接口,其具体的实现有 IotPushStrategy 和 XingePushStrategy,分别对应自研推送平台的推送策略和信鸽平台的推送策略,使用者时针对不同的设备类型使用不同的推送策略即可。


部分代码如下:
/**
 * 推送策略
 * /
public interface PushStrategy {
    /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */

    public CallResult push(AppDeviceVO deviceVO, Object content);
}

IotPushStrategy implements PushStrategy{
        /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */

    public CallResult push(AppDeviceVO deviceVO, Object content){
            //创建自研推送平台需要的推送报文
            Message message = createPushMsg(deviceVO,content);

            //调用推送平台推送接口
            IotMessageService.pushMsg(message);
        }
}

XingePushStrategy implements PushStrategy{
        /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */

    public CallResult push(AppDeviceVO deviceVO, Object content){
            //创建信鸽平台需要的推送报文
            JSONObject jsonObject = createPushMsg(content);

            //调用推送平台推送接口
            if(!XinggePush.pushMsg(message)){
                //降级到长轮询
                ...
            }
        }
}

/**
消息推送Service
*/

MessagePushService{
    pushMsg(AppDeviceVO deviceVO, Object content){
        if(A设备){
            XingePushStrategy.push(deviceVO,content);
        } else if(B设备){
            IotPushStrategy.push(deviceVO,content);
        }
    }
}

总结:将每种通道的推送逻辑封装到了具体的策略中,某种策略的变更不会影响其他策略,由于实现了共同接口,所以策略可以互相替换,对使用者友好。


比如 Java ThreadPoolExecutor 中的任务拒绝策略,当线程池已经饱和的时候会执行拒绝策略,具体的拒绝逻辑被封装到了 RejectedExecutionHandler 的 rejectedExecution 中。


模板设计模式


模式定义


模板的价值就在于骨架的定义,骨架内部将问题处理的流程已经定义好,通用的处理逻辑一般由父类实现,个性化的处理逻辑由子类实现。


比如炒土豆丝和炒麻婆豆腐,大体逻辑都是:

  • 切菜

  • 放油

  • 炒菜

  • 出锅


1,2,4 都差不多,但是第 3 步是不一样的,炒土豆丝得拿铲子翻炒,但是炒麻婆豆腐得拿勺子轻推,否则豆腐会烂(疫情宅在家,学了不少菜)。


使用场景


不同场景的处理流程,部分逻辑是通用的,可以放到父类中作为通用实现,部分逻辑是个性化的,需要子类去个性实现。


实践经验


还是接着之前语音播报的例子来说,后期我们新加了两个需求:

  • 消息推送需要增加 trace。

  • 有些通道推送失败需要重试。


所以现在的流程变成了这样: