被各種巢狀判斷噁心的你,想到狀態模式了嗎?

Single_Yam發表於2021-04-14

今天和大家聊『狀態模式』這個設計模式,也是由於業務上遇到了一個極其難以維護的訂單狀態,不得不去重構。

阿里規約其中就有一條:

image

簡單來說,狀態模式用於消除冗餘的大量『if else』判斷。

舉個例子

業務中有一個訂單表,其中訂單狀態大約有如下十多種,我們維護在一個列舉型別中。

image

接著,我們有一個 service,用於流轉狀態用:

public interface OrderService {

    /**
     * 使用者發起退款,流轉當前訂單到退款狀態
     * @param order
     * @return
     */
    boolean refund(Order order);

    /**
     * 使用者取消訂單
     * @param order
     * @return
     */
    boolean cancle(Order order);
}

然後他的實現類是這樣:

@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public boolean refund(Order order) {
        boolean flag = true;
        //判斷當前訂單狀態
        if (order.getOrderState() == OrderState.WAITSHIP.getCode() ||
                order.getOrderState() == OrderState.PAYED.getCode()){
            //如果是已付款、待發貨,直接退款成功
            order.setOrderState(OrderState.REFUND.getCode());
        }else if (order.getOrderState() == OrderState.ALREADYSHIP.getCode() ||
                    order.getOrderState() == OrderState.ENDED.getCode()){
            //如果是已發貨了或交易完成了,流轉到退款中,等待商家稽核
            order.setOrderState(OrderState.REFUNDING.getCode());
        }else {
            //其他狀態都是異常狀態,拒絕流轉
            flag = false;
        }
        return flag;
    }

    @Override
    public boolean cancle(Order order) {
        boolean flag = true;
        //判斷當前狀態
        if (order.getOrderState() == OrderState.NOTPAY.getCode()){
            //只有未付款狀態可以取消訂單
            order.setOrderState(OrderState.CANCELED.getCode());
        }else {
            flag = false;
        }
        return flag;
    }
}

一個簡單的 refund 流轉退款狀態至少需要上面這麼一大坨的 『if else』判斷,下面的 cancle 取消訂單狀態的流轉稍微簡單些。

這裡我也只精簡了部分程式碼,實際上要複雜的更多,但好在狀態之間的依賴性還沒有太強,沒有出現巢狀多層『if else』判斷,狀態模式怎麼改?

狀態模式

傳統的判斷模式之所以會有很多的『if else』判斷,本質上就是不知道當前訂單實什麼狀態,所以需要判斷當前訂單在不同狀態下該怎麼流轉。

第一步:建立一個抽象狀態基類,在其中定義所有的狀態流轉操作,這裡我只寫了兩個,實際業務中肯定會有很多很多狀態間的跳轉。

@Slf4j
public abstract class AbstOrderState {

    /**
     * 使用者發起退款,流轉當前訂單到退款狀態
     * @param order
     * @return
     */
    public boolean refund(Order order){
        log.info("無法從狀態:{} 流轉到退款狀態",getState());
        return false;
    }

    /**
     * 使用者取消訂單
     * @param order
     * @return
     */
    public boolean cancle(Order order){
        log.info("無法從狀態:{} 流轉到取消訂單狀態",getState());
        return false;
    }

    /**
     * 模板方法,獲取當前狀態
     * @return
     */
    public abstract String getState();
}

第二步,為每一種狀態建立對應的狀態類,並整合抽象狀態基類

image

第三步,分別實現各個狀態下關心的流轉操作,我們舉例其中兩個狀態子類的實現。

這個是已支付狀態

public class PayEdState extends AbstOrderState {

    @Override
    public boolean refund(Order order){
        //當前狀態是已付款,目標狀態是退款
        order.setOrderState(OrderState.REFUND.getCode());
        return true;
    }

    //當前狀態是已付款,目標狀態是取消訂單狀態,無法流轉,異常的狀態
    //無需重寫,使用抽象基類預設實現,返回失敗即可
//    @Override
//    public boolean cancle(Order order){
//        return false;
//    }

    @Override
    public String getState(){
        return OrderState.PAYED.getDesc();
    }
}

這個是未付款狀態

public class NotPayState extends AbstOrderState {

    @Override
    public boolean refund(Order order){
        //當前狀態是未付款,目標狀態是退款,直接成功
        return true;
    }

    @Override
    public boolean cancle(Order order){
        //當前狀態是未付款,目標狀態是取消訂單,直接成功
        return true;
    }

    @Override
    public String getState() {
        return OrderState.NOTPAY.getDesc();
    }
}

第四步,如何使用

@Test
    public void test(){
        //初始化一個訂單,預設未支付狀態
        Order order = new Order();
        order.setOrderState(OrderState.NOTPAY.getCode());

        //我想退款
        AbstOrderState state = OrderState.getStateByCode(order.getOrderState());
        if (state.refund(order)){
            log.info("ok");
        }else {
            log.info("failed");
        }
    }

至此,狀態模式演示完畢,其實細心的你會發現,狀態模式中未出現一行『if else』,但缺點就是多了很多類,但這是抽象性的必然結果。

對比一下

實際訂單狀態這個例子並不是很完美契合狀態模式,因為狀態之間依賴性沒那麼強,很少可能會出現巢狀判斷,但效果是很顯然的。

試想一下,如果以後我的訂單增加了一個狀態叫『凍結狀態』,那麼我只需要建立一個新的狀態類,並只關心我這個凍結狀態相關的流轉操作,重寫一下就好了,根本不用跑到之前的邏輯裡改啊改。

狀態模式還是一個非常優秀的設計模式,推薦大家在專案裡使用起來,除了初始編碼的時候麻煩一點,後續的維護以及擴充套件真的近乎零成本。

近期會整理一個設計模式系列,分別講講 23 種設計模式,感興趣的可以關注下哦~


關注公眾不迷路,一個愛分享的程式設計師。

公眾號回覆「1024」加作者微信一起探討學習!

公眾號回覆「面試題」送你一份面試題以及作者的作答答案

每篇文章用到的所有案例程式碼素材都會上傳我個人 github

https://github.com/SingleYam/overview_java

歡迎來踩!

YangAM 公眾號

相關文章