我们在web开发中,经常使用数据库表中的字段作为“标记”来表示多个“状态”,比如:

我们就以某宝的在线购物流程为例进行分析。在订单表中,使用zt字段来表示定单的状态,常见的状态就有:

当我们想按条件查询各个类型的订单的时候,只需要一个接口,在前端传入相应的状态码就可以了。在dao层大概也就是通过如下的语句进行查询:

select * from orders where zt = #{zt}

如何才能有很高的扩展性?

假设有这么几个“不成需求的需求”:

  1. 我想让待收货的订单按照订单发货时间或者预计送达时间排序,其他的暂且按照订单创建时间排序吧
  2. 想将“待收货”的状态区分开,分为“用户未收到货”和“用户收到货但是未点击确认收货按纽”两种状态

常规方式如何解决?

  • ​需求一(不同的状态处理方式不同):

    这个很容易的,在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?

上边的方式可以完成我们的需求,但是有以下几点不足:

1. 面对“各种各样奇怪的需求”,我们要频繁地修改上边的代码,时间久了,岂不成了渣渣。甚至我们自己都不愿意再去看这些代码了;
2. 如果新增加一个状态表示,也就是给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,代码如下:

/**
 * 微信公众号 “小鱼与Java”
 *
 * 原理很简单,我们写的类实现这个接口,具体可以查阅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的控制反转同样也可以很好地解决这个问题。这么做的好处如下:

  1. “真正的”解决了与我们业务无关的if-else;
  2. 不用前后端再进行状态的表示“约定”,之前用0表示“待付款”,1表示 “待发货”这样的操作,如果记错,那一定会有大问题。现在,使用特定的字符串来表示,也就是说,前端直接传入想要解决这个方案对应的bean,从而少去了“复杂且易出错的约定”环节。

代码地址:

觉得不错的,可以关注我的微信公众号哦

关注微信公众号“小鱼与Java”,获取更多的学习内容

不吹牛X,我真的干掉了if-else-LMLPHP

05-19 12:18