Java收藏

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

2022-03-15  本文已影响0人  Raral

满屏的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

方法。

其他技巧:

上一篇下一篇

猜你喜欢

热点阅读