今天和大家聊『狀態模式』這個設計模式,也是由於業務上遇到了一個極其難以維護的訂單狀態,不得不去重構。
阿里規約其中就有一條:
簡單來說,狀態模式用於消除冗餘的大量『if else』判斷。
舉個例子
業務中有一個訂單表,其中訂單狀態大約有如下十多種,我們維護在一個列舉型別中。
接著,我們有一個 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();
}
第二步,為每一種狀態建立對應的狀態類,並整合抽象狀態基類
第三步,分別實現各個狀態下關心的流轉操作,我們舉例其中兩個狀態子類的實現。
這個是已支付狀態
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 種設計模式,感興趣的可以關注下哦~