前言
在实际的业务开发当中,经常会遇到复杂的业务逻辑,可能部分同学实现出来的代码并没有什么问题,但是代码的可读性很差。本篇文章主要总结一下自己在实际开发中如何避免大面积的 if-else 代码块的问题。补充说明一点,不是说 if-else 不好,而是多层嵌套的 if-else 导致代码可读性差、维护成本高等问题。
现有如下一段示例代码,部分优化技巧是根据这段代码进行的
public class BadCodeDemo { private void getBadCodeBiz(Integer city, ListnewDataList, List 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 oldCollect = oldDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); List newCollect = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); 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()); } } 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()); } } 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()); } } 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()); } } } } } } } }
技巧一:提取方法,拆分逻辑
比如上面这段代码中
if(null != city) { } else { }
这里可以拆分成两段逻辑,核心思想就是逻辑单元最小化,然后合并逻辑单元。
private void getCityNotNull(Integer city, ListnewDataList) { 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); } } } // 合并逻辑流程 private void getBadCodeBiz(Integer city, List newDataList, List oldDataList) { if (city != null) { this.getCityNull(city, newDataList); } else { //此处代码省略 } }
技巧二:分支逻辑提前return
比如 技巧一 中的 getCityNull 方法,我们可以这样写
public void getCityNotNull(Integer city, ListnewDataList) { if (CollectionUtils.isEmpty(newDataList)) { // 提前判断,返回业务逻辑 return; } TestCodeData newData = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).findFirst().orElse(null); if (null != newData) { newData.setCity(city); } }
技巧三:枚举
经过 技巧一 和 技巧二 的优化,文章开头的这段代码被优化成如下所示:
public class BadCodeDemo { public void getBadCodeBiz(Integer city, ListnewDataList, List oldDataList) { if (city != null) { this.getCityNotNull(city, newDataList); } else { this.getCityNull(newDataList, oldDataList); } } private void getCityNotNull(Integer city, List newDataList) { if (CollectionUtils.isEmpty(newDataList)) { // 提前判断,返回业务逻辑 return; } TestCodeData newData = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).findFirst().orElse(null); if (null != newData) { newData.setCity(city); } } private void getCityNull(List newDataList, List oldDataList) { // 提前判断,返回业务逻辑 if (CollectionUtils.isEmpty(oldDataList) && CollectionUtils.isEmpty(newDataList)) { return; } List oldCollect = oldDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); List newCollect = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); // 提前判断,返回业务逻辑 if (CollectionUtils.isEmpty(newCollect) && CollectionUtils.isEmpty(oldCollect)) { return; } 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()); } } 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()); } } 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()); } } 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()); } } } } }
现在利用 枚举 来优化 getCityNull 方法中的 for 循环部分代码,我们可以看到这段代码中有4段逻辑,总体形式如下:
if (newPO.getStartTime() == 0 && newPO.getEndTime() == 12) { //第一段逻辑 } else if (newPO.getStartTime() == 12 && newPO.getEndTime() == 24) { //第二段逻辑 } else if (newPO.getStartTime() == 0 && newPO.getEndTime() == 24) { //第三段逻辑 } else if (newPO.getTimeUnit().equals(Integer.valueOf(1))) { //第四段逻辑 }
按照这个思路利用枚举进行二次优化,将其中的逻辑封装到枚举类中:
public enum TimeEnum { AM("am", "上午") { @Override public void setCity(TestCodeData data, ListoldDataList) { 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 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 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 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 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; } }
然后 getCityNull 方法中 for 循环部分逻辑如下:
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; } }
看完这段代码,逻辑上是没有什么问题的。但它有一个隐藏的缺陷,如果后期又增加很多渠道的时候,你该怎么办?继续 else if 吗?其实我们可以利用函数式接口优化,当然设计模式也可以优化。这里我只是举例使用一下函数式接口的使用方式。
@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> 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 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 TouTiaoAwardService extends AwardAbstract { @Override public Boolean award(String userId) { log.info("头条渠道用户{}奖励50元红包!", userId); return Boolean.TRUE; } } // 微信渠道发放奖励业务 public class WeChatAwardService extends AwardAbstract { @Override public Boolean award(String userId) { log.info("微信渠道用户{}奖励100元红包!", userId); return Boolean.TRUE; } }
- 利用工厂模式获取实例对象
public class AwardFactory { public static AwardAbstract getAwardInstance(String source) { if ("toutiao".equals(source)) { return new TouTiaoAwardService(); } else if ("wx".equals(source)) { return new WeChatAwardService(); } return null; } }
- 业务入口处根据不同渠道执行不同的发放逻辑
@PostMapping("/reward2") public void reward2(String userId, String source) { AwardAbstract instance = AwardFactory.getAwardInstance(source); if (null != instance) { instance.award(userId); } }
使用技巧二:策略模式+模板方法+工厂模式+单例模式
还是以营销拉新为业务场景来说明,这个业务流程再增加一些复杂度,比如发放奖励之前要进行 身份验证、风控验证 等一些列的校验,此时你的业务流程该如何实现更清晰简洁呢!
- 定义业务策略接口
/** 策略业务接口 */ public interface AwardStrategy { /** * 奖励发放接口 */ MapawardStrategy(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); }
- 实现不同渠道的奖励业务
@Slf4j @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) { log.info("头条渠道用户{}奖励50元红包!", userId); return Boolean.TRUE; } } @Slf4j @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) { log.info("微信渠道用户{}奖励100元红包!", userId); return Boolean.TRUE; } }
- 定义工厂方法,对外统一暴露业务调用入口
@Component public class AwardStrategyFactory implements ApplicationContextAware { private final static MapMAP = new HashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map beanTypeMap = applicationContext.getBeansOfType(AwardStrategy.class); beanTypeMap.values().forEach(strategyObj -> MAP.put(strategyObj.getSource(), strategyObj)); } /** * 对外统一暴露的工厂方法 */ public Boolean getAwardResult(String userId, String source) { AwardStrategy strategy = MAP.get(source); if (Objects.isNull(strategy)) { throw new RuntimeException("渠道异常!"); } return strategy.awardStrategy(userId); } /** * 静态内部类创建单例工厂对象 */ private static class CreateFactorySingleton { private static AwardStrategyFactory factory = new AwardStrategyFactory(); } public static AwardStrategyFactory getInstance() { return CreateFactorySingleton.factory; } }
- 业务入口方法
@RestController @RequestMapping("/activity") public class ActivityController { @PostMapping("/reward3") public void reward3(String userId, String source) { AwardStrategyFactory.getInstance().getAwardResult(userId, source); } }
假如发起请求: POST http://localhost:8080/activity/reward3?userId=fei&source=wx
2022-02-20 12:23:27.716 INFO 20769 --- [nio-8080-exec-1] c.a.c.e.o.c.p.s.BaseAwardTemplate : fei 执行身份验证! 2022-02-20 12:23:27.719 INFO 20769 --- [nio-8080-exec-1] c.a.c.e.o.c.p.s.BaseAwardTemplate : fei 执行风控校验! 2022-02-20 12:23:27.719 INFO 20769 --- [nio-8080-exec-1] a.c.e.o.c.p.s.WeChatAwardStrategyService : 微信渠道用户fei奖励100元红包!
其他技巧
- 使用三目运算符
- 相同业务逻辑提取复用
写在最后
不论使用那种技巧,首先是我们在业务代码开发过程中一定要多思考,将复杂的业务逻辑能通过简洁的代码表现出来,这才是你的核心能力之一,而不是一个 curd boy。与君共勉,共同进步!
Gitee 获取源码
到此这篇关于java实战技巧之if-else代码优化技巧的文章就介绍到这了,更多相关java if-else代码优化技巧内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!