满屏的if-else,看我怎么消灭你!
Posted 码农突围
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了满屏的if-else,看我怎么消灭你!相关的知识,希望对你有一定的参考价值。
点击上方“码农突围”,马上关注
这里是码农充电第一站,回复“666”,获取一份专属大礼包
真爱,请设置“星标”或点个“在看”
文章来源:http://u6.gg/k376d
在实际的业务开发当中,经常会遇到复杂的业务逻辑,可能部分同学实现出来的代码并没有什么问题,但是代码的可读性很差。
本篇文章主要总结一下自己在实际开发中如何避免大面积的 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、卧槽!字节跳动《算法中文手册》火了,完整版 PDF 开放下载!
2、计算机基础知识总结与操作系统 PDF 下载
3、艾玛,终于来了!《LeetCode Java版题解》.PDF
4、Github 10K+,《LeetCode刷题C/C++版答案》出炉.PDF
欢迎添加鱼哥个人微信:smartfish2020,进粉丝群或围观朋友圈
以上是关于满屏的if-else,看我怎么消灭你!的主要内容,如果未能解决你的问题,请参考以下文章
别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!