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

咱們在web開發中,常用數據庫表中的字段做爲「標記」來表示多個「狀態」,好比:前端

咱們就以某寶的在線購物流程爲例進行分析。在訂單表中,使用zt字段來表示定單的狀態,常見的狀態就有:java

狀態碼 狀態說明
0 待付款
1 待發貨
2 待收貨
3 待評價
4 售後

當咱們想按條件查詢各個類型的訂單的時候,只須要一個接口,在前端傳入相應的狀態碼就能夠了。在dao層大概也就是經過以下的語句進行查詢:git

select * from orders where zt = #{zt}

如何纔能有很高的擴展性?

假設有這麼幾個「不成需求的需求」:web

  1. 我想讓待收貨的訂單按照訂單發貨時間或者預計送達時間排序,其餘的暫且按照訂單建立時間排序吧
  2. 想將「待收貨」的狀態區分開,分爲「用戶未收到貨」和「用戶收到貨可是未點擊確認收貨按紐」兩種狀態

常規方式如何解決?

  • ​ 需求一(不一樣的狀態處理方式不一樣):spring

    這個很容易的,在sevice層添加一個判斷就能夠,其餘的代碼不用改,代碼以下:sql

// 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代碼中添加新的狀態判斷不就行了。app

思考如何幹掉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」,獲取更多的學習內容

相關文章
相關標籤/搜索