阿里java开发三年程序员:不吹牛X,我轻松干掉了if-else
前言
虽然 if else 是必须的,但滥用 if else 会对代码的可读性、可维护性造成很大伤害,进而危害到整个软件系统。
现在软件开发领域出现了很多新技术、新概念,但 if...else 这种基本的程序形式并没有发生太大变化。
使用好 if else 不仅对于现在,而且对于将来,都是十分有意义的。今天我们就来看看如何“干掉”代码中的 if else,还代码以清爽。
我们在web开发中,经常使用数据库表中的字段作为“标记”来表示多个“状态”,比如:
我们就以某宝的在线购物流程为例进行分析。在订单表中,使用zt字段来表示订单的状态,常见的状态就有:
当我们想按条件查询各个类型的订单的时候,只需要一个接口,在前端传入相应的状态码就可以了。在dao层大概也就是通过如下的语句进行查询:
select * from orders where zt = #{zt}
如何才能有很高的扩展性?
假设有这么几个“不成需求的需求”:
- 我想让待收货的订单按照订单发货时间或者预计送达时间排序,其他的暂且按照订单创建时间排序吧
- 想将“待收货”的状态区分开,分为“用户未收到货”和“用户收到货但是未点击确认收货按钮”两种状态
常规方式如何解决?
- 需求一(不同的状态处理方式不同):这个很容易的,在sevice层添加一个判断就可以,其他的代码不用改,代码如下:
// 2 表示待收货
if(zt == 2){
//按照需求,按照订单发货时间或者预计送达时间排序
}else{
//其他状态的订单,全部按照订单创建时间排序
}
上边这个代码的修改量已经很小了,但是如果我要把各种不同的状态订单全部按照不同的排序方式排序呢?你可能会写如下代码
if(zt==0){
// 待付款的订单处理代码...
}else if(zt==1){
}else if(zt==2){
}else if(zt==3){
}else if(zt==4){
}
上边代码太low了,有些小伙伴可能会使用switch进行优化(这里就不写代码了,因为和上边并没有任何区别)。
- 需求二(添加一个新的状态表示):这也很easy啊,直接在上边的if-else或者switch代码中添加新的状态判断不就好了。
思考如何干掉if-else?
上边的方式可以完成我们的需求,但是有以下几点不足:
- 面对“各种各样奇怪的需求”,我们要频繁地修改上边的代码,时间久了,岂不成了渣渣。甚至我们自己都不愿意再去看这些代码了;
- 如果新增加一个状态表示,也就是给zt字段新的状态含义表示,我们又要添加if-else,这太复杂了。
使用策略模式来解决if-else的问题
是的,就是使用策略模式来解决进行太多的状态判断代码就是一个好办法。比如,就上边每一个if-else中的代码抽成一个类或者方法进行处理。
主要的代码我就不写了,因为下边才是我们的主菜,这里说的这种方式只能解决if-else里边的代码复杂问题,将代码进行一定程度上的解耦。但并没有实质地解决if-else的问题,而且这也是网上大多数的解决办法。
如果对策略模式不太了解的小伙伴,可以看下这篇文章,不看也没关系,在下边你会看到怎么用的。策略模式的学习之道
尝试使用Spring来配合策略模式
程序设计的一大原则“对扩展开放,对修改关闭”,定义一个接口类,用来查询不同状态的订单列表。如下:
public interface OrderService {
/**
* 查询对应状态的订单列表
* @param zt
* @return
*/
List<Order> getOrderList(String zt);
}
然后根据不同的订单状态创建不同的实现类,比如,“待付款”的订单查询类如下:
虽然以下的命名方式属于错误示范,但是却能很好地理解
@Service("orderServiceDfk") // 这个命名确实很不友好,但是我相信你能理解哈
public class PendingParymentOrderSeviceImpl implements OrderService {
/**
* 查询待付款的订单列表
*
* @param zt
* @return
*/
@Override
public List<Order> getOrderList(String zt) {
//这里要利用dao层从数据库中查询出来相应的订单列表
return null;
}
看了这两个类的代码,我相信小伙伴们应该能理解了要怎么做了,就是根据前端传来不同的zt值,后台使用不同的类来处理,但是我们可以通过Spring来完全去掉if-else。
我们的controller层代码如下:
@RestController
public class OrderController {
private String orderServiceBeanNamePrefix = "orderService";
@RequestMapping("getOrderList/{zt}")
public List<Order> getOrderList(@PathVariable("zt") String zt) {
//获取对应的处理状态的bean来处理
//就通过这样一句代码,完全解决了if-else的判断逻辑
OrderService orderService = (OrderService) SpingContext.getBean(orderServiceBeanNamePrefix + zt);
List<Order> orderList = orderService.getOrderList();
return orderList;
}
}
上边用了一个工具类,就是从Spring 容器中获取相应的bean,代码如下:
/**
*
* 原理很简单,我们写的类实现这个接口,具体可以查阅Spring生命周期相关内容
* Spring会自动调用其中的setApplicationContext方法,传入Spring容器上下文
* 我们就在这里把Spring上下文保存下来
*
* @date 2020/5/18
* @auther Lyn4ever
*/
@Component
public class SpingContext implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 根据name从Spring容器中获取bean
* @param name
* @return
*/
public static Object getBean(String name){
return applicationContext.getBean(name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("我保存了Spring上下文");
applicationContext = applicationContext;
}
}
总结:
解决if-else的思路就是使用策略模式,针对不同“状态”的订单,使用不同的类来处理逻辑,这样就可以很好地进行了“解耦”操作。但是,如果新增一个“状态表示 ”,我们就要在主逻辑处添加if-else进行判断要用哪个类来处理。
而解决这个“判断 ”的中使用的if-else就有很多方法:抽象工厂也是一个不错的方法。而我们使用Spring的控制反转同样也可以很好地解决这个问题。这么做的好处如下:
- “真正的”解决了与我们业务无关的if-else;
- 不用前后端再进行状态的表示“约定”,之前用0表示“待付款”,1表示 “待发货”这样的操作,如果记错,那一定会有大问题。现在,使用特定的字符串来表示,也就是说,前端直接传入想要解决这个方案对应的bean,从而少去了“复杂且易出错的约定”环节。
正如前言所说,if else 是代码中的重要组成部分,但是过度、不必要地使用 if else,会对代码的可读性、可扩展性造成负面影响,进而影响到整个软件系统。
“干掉”if else 的能力高低反映的是程序员对软件重构、设计模式、面向对象设计、架构模式、数据结构等多方面技术的综合运用能力,反映的是程序员的内功。
要合理使用 if else,不能没有设计,也不能过度设计。这些对技术的综合、合理的运用都需要程序员在工作中不断的摸索总结。