⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 u6.gg/k376d 「大飞学习笔记」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

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

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

现有如下一段示例代码,部分优化技巧是根据这段代码进行的:

public class BadCodeDemo {

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 (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, List<TestCodeData> newDataList) {
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<TestCodeData> newDataList, List<TestCodeData> oldDataList) {
if (city != null) {
this.getCityNull(city, newDataList);
} else {
//此处代码省略
}
}

技巧二:分支逻辑提前return

比如“技巧一”中的 getCityNull 方法,我们可以这样写:

public void getCityNotNull(Integer city, List<TestCodeData> 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);
}
}

技巧三:枚举

经过“技巧一”和“技巧二”的优化,文章开头的这段代码被优化成如下所示:

public class BadCodeDemo {

public void getBadCodeBiz(Integer city, List<TestCodeData> newDataList, List<TestCodeData> oldDataList) {
if (city != null) {
this.getCityNotNull(city, newDataList);
} else {
this.getCityNull(newDataList, oldDataList);
}
}

private void getCityNotNull(Integer city, List<TestCodeData> 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<TestCodeData> newDataList, List<TestCodeData> oldDataList) {
// 提前判断,返回业务逻辑
if (CollectionUtils.isEmpty(oldDataList) && CollectionUtils.isEmpty(newDataList)) {
return;
}

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 (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, 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;
}
}

然后 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<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 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 {
/**
* 奖励发放接口
*/
Map<String, 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);
}

实现不同渠道的奖励业务:

@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 Map<String, AwardStrategy> MAP = new HashMap<>();

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, AwardStrategy> 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。与君共勉,共同进步!

文章目录
  1. 1. 技巧一:提取方法,拆分逻辑
  2. 2. 技巧二:分支逻辑提前return
  3. 3. 技巧三:枚举
  4. 4. 技巧四:函数式接口
  5. 5. 技巧五:设计模式
    1. 5.0.0.1. 使用技巧一:工厂模式+抽象类
    2. 5.0.0.2. 使用技巧二:策略模式+模板方法+工厂模式+单例模式
  • 6. 其他技巧
  • 7. 写在最后