美文网首页Java收藏
满屏的if-else,设计模式怎么消灭你!

满屏的if-else,设计模式怎么消灭你!

作者: Raral | 来源:发表于2022-03-15 13:53 被阅读0次

    满屏的if-else,看我怎么消灭你!

    在实际的业务开发当中,经常会遇到复杂的业务逻辑,可能部分同学实现出来的代码没有什么问题,但是代码的可读性很差

    本篇文章主要总结一下自己在实际开发中如何避免大面积的 if-else 代码块的问题。补充说明一点,不是说 if-else 不好,而是多层嵌套的 if-else 导致代码可读性差、维护成本高等问题。

    public class BadCodeDemo {
        
        /**
            city: 城市
            newDataList: 老数据
            oldDataList: 新数据
        */
        private void getBadCodeBiz(Integer city, List<TestCodeData> newDataList, List<TestCodeData> oldDataList) {
            //判断城市是否为空
            if (city != null) {
    
                if (newDataList != null && newDataList.size() > 0) {
                    TestCodeData newData = newDataList.stream().filter(p -> {
                        if (p.getIsHoliday() == 1) {
                            return true;
                        }
                        return false;
                    }).findFirst().orElse(null);
                    if (newData != null) {
                        newData.setCity(city);
                    }
                }
    
            } else {//断城市不是空
    
                if (oldDataList != null && newDataList != null) {
                    List<TestCodeData> oldCollect = oldDataList.stream().filter(p -> {
                        if (p.getIsHoliday() == 1) {
                            return true;
                        }
                        return false;
    
                    }).collect(Collectors.toList());
                    List<TestCodeData> newCollect = newDataList.stream().filter(p -> {
                        if (p.getIsHoliday() == 1) {
                            return true;
                        }
                        return false;
                    }).collect(Collectors.toList());
                    
                    //if 1
                    if (newCollect != null && newCollect.size() > 0 && oldCollect != null && oldCollect.size() > 0) {
                        for (TestCodeData newPO : newCollect) {
                            if (newPO.getStartTime() == 0 && newPO.getEndTime() == 12) {
                                TestCodeData po = oldCollect.stream().filter(p -> p.getStartTime() == 0
                                        && (p.getEndTime() == 12 || p.getEndTime() == 24)).findFirst().orElse(null);
                                if (po != null) {
                                    newPO.setCity(po.getCity());
                                }
                             //if 2
                            } else if (newPO.getStartTime() == 12 && newPO.getEndTime() == 24) {
                                TestCodeData po = oldCollect.stream().filter(
                                        p -> (p.getStartTime() == 12 || p.getStartTime() == 0)
                                                && p.getEndTime() == 24).findFirst().orElse(null);
                                if (po != null) {
                                    newPO.setCity(po.getCity());
                                }
                              //if 3    
                            } else if (newPO.getStartTime() == 0 && newPO.getEndTime() == 24) {
                                TestCodeData po = oldCollect.stream().filter(
                                        p -> p.getStartTime() == 0 && p.getEndTime() == 24).findFirst().orElse(null);
                                if (po == null) {
                                    po = oldCollect.stream().filter(
                                            p -> p.getStartTime() == 0 && p.getEndTime() == 12).findFirst().orElse(null);
                                }
                                if (po == null) {
                                    po = oldCollect.stream().filter(
                                            p -> p.getStartTime() == 12 && p.getEndTime() == 24).findFirst().orElse(null);
                                }
                                if (po != null) {
                                    newPO.setCity(po.getCity());
                                }
                              //if 4     
                            } else if (newPO.getTimeUnit().equals(Integer.valueOf(1))) {
                                TestCodeData po = oldCollect.stream().filter(
                                        e -> e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null);
                                if (po != null) {
                                    newPO.setCity(po.getCity());
                                }
                            }
                        }
                    }
    
                }
            }
        }
    }
    

    枚举

    ublic enum TimeEnum {
    
        AM("am", "上午") {
            @Override
            public void setCity(TestCodeData data, List<TestCodeData> oldDataList) {
                TestCodeData po = oldDataList.stream().filter(p -> p.getStartTime() == 0
                        && (p.getEndTime() == 12 || p.getEndTime() == 24)).findFirst().orElse(null);
                if (null != po) {
                    data.setCity(po.getCity());
                }
            }
        },
        PM("pm", "下午") {
            @Override
            public void setCity(TestCodeData data, List<TestCodeData> oldCollect) {
                TestCodeData po = oldCollect.stream().filter(
                        p -> (p.getStartTime() == 12 || p.getStartTime() == 0)
                                && p.getEndTime() == 24).findFirst().orElse(null);
                if (po != null) {
                    data.setCity(po.getCity());
                }
            }
        },
        DAY("day", "全天") {
            @Override
            public void setCity(TestCodeData data, List<TestCodeData> oldCollect) {
                TestCodeData po = oldCollect.stream().filter(
                        p -> p.getStartTime() == 0 && p.getEndTime() == 24).findFirst().orElse(null);
                if (po == null) {
                    po = oldCollect.stream().filter(
                            p -> p.getStartTime() == 0 && p.getEndTime() == 12).findFirst().orElse(null);
                }
                if (po == null) {
                    po = oldCollect.stream().filter(
                            p -> p.getStartTime() == 12 && p.getEndTime() == 24).findFirst().orElse(null);
                }
                if (po != null) {
                    data.setCity(po.getCity());
                }
            }
        },
        HOUR("hour", "小时") {
            @Override
            public void setCity(TestCodeData data, List<TestCodeData> oldCollect) {
                TestCodeData po = oldCollect.stream().filter(
                        e -> e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null);
                if (po != null) {
                    data.setCity(po.getCity());
                }
            }
        };
    
        public abstract void setCity(TestCodeData data, List<TestCodeData> oldCollect);
    
        private String code;
        private String desc;
    
        TimeEnum(String code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    }
    
    for (TestCodeData data : newCollect) {
        if (data.getStartTime() == 0 && data.getEndTime() == 12) {
            TimeEnum.AM.setCity(data, oldCollect);
        } else if (data.getStartTime() == 12 && data.getEndTime() == 24) {
            TimeEnum.PM.setCity(data, oldCollect);
        } else if (data.getStartTime() == 0 && data.getEndTime() == 24) {
            TimeEnum.DAY.setCity(data, oldCollect);
        } else if (data.getTimeUnit().equals(Integer.valueOf(1))) {
            TimeEnum.HOUR.setCity(data, oldCollect);
        }
    }
    

    其实在这个业务场景中使用枚举并不是特别合适,如果在遍历对象时,我们就知道要执行哪个枚举类型,此时最合适,伪代码如下:

    for (TestCodeData data : newCollect) {
          String code = "am";  // 这里假设 code 变量是从 data 中获取的
          TimeEnum.valueOf(code).setCity(data, oldCollect);
    }
    

    函数式接口

    业务场景描述:比如让你做一个简单的营销拉新活动,这个活动投放到不同的渠道,不同渠道过来的用户奖励不一样。

    现假设在头条、微信等渠道都投放了该活动。此时你的代码可能会写出如下形式:

    @RestController
    @RequestMapping("/activity")
    public class ActivityController {
        @Resource
        private AwardService awardService;
    
        @PostMapping("/reward")
        public void reward(String userId, String source) {
            if ("toutiao".equals(source)) {
                awardService.toutiaoReward(userId);
            } else if ("wx".equals(source)) {
                awardService.wxReward(userId);
            }
        }
    }
    
    @Service
    public class AwardService {
        private static final Logger log = LoggerFactory.getLogger(AwardService.class);
    
        public Boolean toutiaoReward(String userId) {
            log.info("头条渠道用户{}奖励50元红包!", userId);
            return Boolean.TRUE;
        }
    
        public Boolean wxReward(String userId) {
            log.info("微信渠道用户{}奖励100元红包!", userId);
            return Boolean.TRUE;
        }
    }
    

    函数式优化

    @RestController
    @RequestMapping("/activity")
    public class ActivityController {
        @Resource
        private AwardService awardService;
    
        @PostMapping("/reward")
        public void reward(String userId, String source) {
            awardService.getRewardResult(userId, source);
        }
    }
    
    @Service
    public class AwardService {
        private static final Logger log = LoggerFactory.getLogger(AwardService.class);
        private Map<String, BiFunction<String, String, Boolean>> sourceMap = new HashMap<>();
    
        @PostConstruct
        private void dispatcher() {
            sourceMap.put("wx", (userId, source) -> this.wxReward(userId));
            sourceMap.put("toutiao", (userId, source) -> this.toutiaoReward(userId));
        }
    
        public Boolean getRewardResult(String userId, String source) {
            BiFunction<String, String, Boolean> result = sourceMap.get(source);
            if (null != result) {
                return result.apply(userId, source);
            }
            return Boolean.FALSE;
        }
    
        private Boolean toutiaoReward(String userId) {
            log.info("头条渠道用户{}奖励50元红包!", userId);
            return Boolean.TRUE;
        }
    
        private Boolean wxReward(String userId) {
            log.info("微信渠道用户{}奖励100元红包!", userId);
            return Boolean.TRUE;
        }
    }
    

    针对一些复杂的业务场景,业务参数很多时,可以利用 @FunctionalInterface 自定义函数式接口来满足你的业务需求,使用原理和本例并无差别。

    设计模式

    设计模式对于 if-else 的优化,我个人觉得有些重,但是也是一种优化方式。设计模式适合使用在大的业务流程和场景中使用,针对代码块中的 if-else 逻辑优化不推荐使用。

    常用的设计模式有:

    • 策略模式

    • 模板方法

    • 工厂模式

    • 单例模式

    工厂模式+抽象类

    抽象业务类

    /**
     *抽象业务接口
     * */
    public abstract class AwardAbstract {
        public abstract Boolean award(String userId);
    }
    
    

    不同渠道实现业务类

    // 头条渠道发放奖励业务
    public class TouTiaoService extends AwardAbstract{
        @Override
        public Boolean award(String userId) {
            System.out.println("头条渠道用户{}奖励50元红包!");
            return Boolean.TRUE;
        }
    }
    
    // 微信渠道发放奖励业务
    public class WxService extends AwardAbstract{
        @Override
        public Boolean award(String userId) {
            System.out.println("微信渠道用户{}奖励50元红包!");
            return Boolean.TRUE;
        }
    }
    
    
    

    工厂类(根据不同的渠道生产对应的实现类)

    //工厂模式
    public class AwardFactory {
        //生产一个AwardAbstract抽象类
        public static AwardAbstract getAwardInstance(String source) {
            if("toutiao".equals(source)) {
                return new TouTiaoService();
            }else if("wx".equals(source)) {
                return new WxService();
            }
            return null;
        }
    }
    
    

    controller 调用

    @PostMapping("/reward2")
        public void reward2(String userId, String source) {
            AwardAbstract awardInstance = AwardFactory.getAwardInstance(source);
            Boolean award = awardInstance.award(userId);
        }
    

    策略模式+模板方法+工厂模式+单例模式(适合比较复杂业务场景)

    是以营销拉新为业务场景来说明,这个业务流程再增加一些复杂度,比如发放奖励之前要进行身份验证、风控验证等一些列的校验,此时你的业务流程该如何实现更清晰简洁呢?

    定义业务策略接口:

    //策略业务接口
    public interface AwardStrategy {
        /**
         * 奖励发放接口,
         * */
        Boolean awardStrategy(String userId);
    
        /**
         * 获取策略标识,即不同渠道的来源标识
         * */
        String getSource();
    }
    
    

    定义发放奖励抽象类

    //定义发放奖励模板,抽象类
    public abstract class BaseAwardTemplate {
        private static final Logger log = LoggerFactory.getLogger(BaseAwardTemplate.class);
        //奖励发放模板方法
        public Boolean awardTemplate(String userId) {
            this.authentication(userId);
            this.risk(userId);
            return this.awardRecord(userId);
    
    
        }
    
        // 身份验证
        protected void authentication(String userId) {
            log.info("{} 执行身份验证!", userId);
        }
        // 风控验证
        protected void risk(String userId) {
            log.info("{} 执行风控校验!", userId);
        }
        // 执行奖励发放(核心方法)抽象方法
        protected abstract Boolean awardRecord(String userId);
    
    }
    
    

    定义不同渠道对应的业务实现类

    
    //头条实现类
    @Log4j
    @Service
    public class ToutiaoAwardStrategyService extends BaseAwardTemplate implements AwardStrategy{
        @Override
        public Boolean awardStrategy(String userId) {
            return super.awardTemplate(userId);
        }
    
        @Override
        public String getSource() {
            return "toutiao";
        }
    
        @Override
        protected Boolean awardRecord(String userId) {
            System.out.println("头条渠道用户{}奖励50元红包!");
            return Boolean.TRUE;
        }
    }
    
    
    
    
    
    //微信实现类
    @Log4j
    @Service
    public class WeChatAwardStrategyService extends BaseAwardTemplate implements AwardStrategy {
        @Override
        public Boolean awardStrategy(String userId) {
            return super.awardTemplate(userId);
        }
    
        @Override
        public String getSource() {
            return "wx";
        }
    
        @Override
        protected Boolean awardRecord(String userId) {
            System.out.println("微信渠道用户{}奖励50元红包!");
            return Boolean.TRUE;
        }
    }
    
    

    工厂模式(不同渠道生产 不同对象)

    
    //工厂
    @Component
    @Slf4j
    public class AwardStrategyFactory implements ApplicationContextAware {
        private final static Map<String, AwardStrategy> MAP = new HashMap<>();
    
        //将不同渠道的实现类,存放到map
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Map<String, AwardStrategy> beanTypeMap  = applicationContext.getBeansOfType(AwardStrategy.class);
            beanTypeMap.values().forEach(v -> MAP.put(v.getSource(), v));
            log.info("==加载beans==");
        }
    
        public Boolean getAwardResult(String userId, String source) {
    
            AwardStrategy awardStrategy = MAP.get(source);
            if(Objects.isNull(awardStrategy)) {
                throw new RuntimeException("渠道异常");
            }
            return awardStrategy.awardStrategy(userId);
        }
    
    
        /**
         * 静态内部类创建单例工厂对象
         */
        private static class CreateFactorySingleton {
            private static AwardStrategyFactory factory = new AwardStrategyFactory();
        }
    
        public static AwardStrategyFactory getInstance() {
            return CreateFactorySingleton.factory;
        }
    
    
    
    }
    
    

    controller 层调用

     @PostMapping("/reward3")
        public void reward3(String userId, String source) {
             userId = "123";
             source = "wx";
            Boolean awardResult = AwardStrategyFactory.getInstance().getAwardResult(userId, source);
        }
    

    UML图:

    [图片上传失败...(image-4ffb9b-1647323614183)]

    ApplicationContextAware 使用

    spring 加载bean对象时,如果bean对象实现了 ApplicationContextAware 接口时,会自动调用ApplicationContextAware 接口中的

    public void setApplicationContext(ApplicationContext context) throws BeansException
    

    方法。

    其他技巧:

    • 使用三目运算符
    • 相同业务逻辑提取复用

    相关文章

      网友评论

        本文标题:满屏的if-else,设计模式怎么消灭你!

        本文链接:https://www.haomeiwen.com/subject/qwhydrtx.html