首先簡述一個(gè)場景要拂,訂單的狀態(tài)流轉(zhuǎn)。 一個(gè)訂單會(huì)有很多種狀態(tài):臨時(shí)單站楚、已下單脱惰、待支付、已支付窿春、已完成拉一、退款中等等采盒。每一種狀態(tài)都和其扭轉(zhuǎn)前的狀態(tài)、在扭轉(zhuǎn)前狀態(tài)所執(zhí)行的操作有關(guān)舅踪。
一 引子
舉例一個(gè)過程:用戶將商品加入購物車纽甘,在后臺(tái)生成了一個(gè)所謂的“臨時(shí)單”,這個(gè)訂單實(shí)際上還沒有正式生成抽碌,因?yàn)橛脩羧匀粵]有點(diǎn)擊下單悍赢。只有當(dāng)用戶下單后,這個(gè)“臨時(shí)單”才可以轉(zhuǎn)化為一個(gè)“待支付的訂單”货徙。那么這個(gè)過程中:只有將一個(gè)處于“臨時(shí)單”狀態(tài)的訂單執(zhí)行下單操作左权,才能得到一個(gè)狀態(tài)為“待支付”的訂單。 即--一個(gè)前置狀態(tài)+一個(gè)恰當(dāng)?shù)牟僮鞒占眨拍芘まD(zhuǎn)訂單的狀態(tài)赏迟。在這個(gè)過程中,如果是硬編碼蠢棱,那么我們需要一系列的 if...else 語句來檢查訂單的當(dāng)前狀態(tài)锌杀、可執(zhí)行操作以及這兩個(gè)的組合得到的下一個(gè)應(yīng)該被流轉(zhuǎn)的狀態(tài)值。如果訂單的狀態(tài)流轉(zhuǎn)很復(fù)雜的話泻仙,寫出來的邏輯就會(huì)很復(fù)雜糕再,并且可讀性很低。后期的維護(hù)就是一個(gè)坑玉转。
二 狀態(tài)設(shè)計(jì)模式與訂單狀態(tài)流轉(zhuǎn)
處理這個(gè)問題突想,我們可以使用 狀態(tài)機(jī)設(shè)計(jì)模式 來處理。對(duì)應(yīng)到實(shí)踐究抓,就是狀態(tài)機(jī)猾担。
關(guān)于狀態(tài)機(jī)設(shè)計(jì)模式的具體內(nèi)容,可以自行百度刺下。這里用簡單的一句話來概括的話:對(duì)象的內(nèi)部狀態(tài)隨外部執(zhí)行條件的變化而變化绑嘹。再映射到訂單狀態(tài)的流轉(zhuǎn)上:訂單的狀態(tài),隨訂單當(dāng)前狀態(tài)和目前執(zhí)行操作的組合而變化橘茉。
三 編碼前的抽象與設(shè)計(jì)
圖示模擬一個(gè)訂單狀態(tài)的流轉(zhuǎn)流程圾叼。從一個(gè)臨時(shí)訂單開始,每當(dāng)訂單處于某一個(gè)已知的狀態(tài)的時(shí)候捺癞,要想讓這個(gè)訂單改變狀態(tài),就需要我們?nèi)?zhí)行對(duì)應(yīng)的操作构挤。
從狀態(tài)機(jī)角度來說髓介,我們先將各種信息進(jìn)行抽象和處理
3.1 代碼抽象
編寫對(duì)應(yīng)訂單狀態(tài)枚舉類
public enum OrderStatusEnum {
CREATE_EVENT(1, "創(chuàng)建訂單"),
FORMAL_EVENT(2, "正式訂單"),
NEED_PAY(3, "待支付"),
PAY_DONE(4, "已支付"),
ORDER_FINISHED(5, "訂單已完成"),
ORDER_CANCEL(6, "訂單已取消");
OrderStatusEnum(int status, String desc) {
this.status = status;
this.desc = desc;
}
public int status;
public String desc;
}
枚舉類中先準(zhǔn)備好需要用的狀態(tài)信息。
先用一張圖來描述整個(gè)工作機(jī)制:
然后是需要的核心代碼部分:一個(gè)管理訂單狀態(tài)的中轉(zhuǎn)站manager類筋现,一組用于扭轉(zhuǎn)訂單狀態(tài)的operator類唐础,一組扭轉(zhuǎn)完訂單狀態(tài)后執(zhí)行后續(xù)邏輯操作的processor類箱歧。
manager類需要根據(jù)對(duì)應(yīng)傳入的當(dāng)前訂單狀態(tài)、要對(duì)該訂單執(zhí)行操作來得到這個(gè)訂單的結(jié)果狀態(tài)(依靠對(duì)應(yīng)的opertor類)一膨,然后執(zhí)行一系列需要的業(yè)務(wù)邏輯操作(編寫對(duì)應(yīng)的processor類)呀邢。這樣的好處就是將訂單狀態(tài)流轉(zhuǎn)和對(duì)應(yīng)的業(yè)務(wù)處理解耦。并且也不會(huì)再有一堆繁雜的 if...else 操作豹绪。每當(dāng)需要新的訂單狀態(tài)流轉(zhuǎn)操作的時(shí)候价淌,可以去編寫對(duì)應(yīng)的一套o(hù)perator和processor組件來完成,和已有業(yè)務(wù)的分離度很高瞒津。
接下來貼代碼舉例
/**
* 訂單狀態(tài)流轉(zhuǎn)管理器--狀態(tài)機(jī)核心組件
* @author carpeng.gao@qunar.com
* @date 2018/10/29 19:21
**/
@Component
public class OrderStateManager {
Map<Integer, AbstractOrderOperator> orderOperatorMaps = new HashMap<Integer, AbstractOrderOperator>();
Map<Integer, AbstractOrderProcessor> orderProcessorMaps = new HashMap<Integer, AbstractOrderProcessor>();
public OrderStateManager() { }
/**
* 狀態(tài)流轉(zhuǎn)方法
* @param orderId 訂單id
* @param event 流轉(zhuǎn)的訂單操作事件
* @param status 當(dāng)前訂單狀態(tài)
* @return 扭轉(zhuǎn)后的訂單狀態(tài)
*/
public int handleEvent(final String orderId, OrderStatusEnum event, final int status) {
if (this.isFinalStatus(status)) {
throw new IllegalArgumentException("handle event can't process final state order.");
}
// 獲取對(duì)應(yīng)處理器,根據(jù)入?yún)顟B(tài)和時(shí)間獲取訂單流轉(zhuǎn)的結(jié)果狀態(tài)
AbstractOrderOperator abstractOrderOperator = this.getStateOperator(event);
int resState = abstractOrderOperator.handleEvent(status, event);
// 得到結(jié)果狀態(tài)蝉衣,在對(duì)應(yīng)的processor中處理訂單數(shù)據(jù)及其相關(guān)信息
AbstractOrderProcessor orderProcessor = this.getOrderProcessor(event);
if (!orderProcessor.process(orderId, resState)) {
throw new IllegalStateException(String.format("訂單狀態(tài)流轉(zhuǎn)失敗,訂單id:%s", orderId));
}
return resState;
}
/**
* 根據(jù)入?yún)顟B(tài)枚舉實(shí)例獲取對(duì)應(yīng)的狀態(tài)處理器
* @param event event
* @return
*/
private AbstractOrderOperator getStateOperator(OrderStatusEnum event) {
AbstractOrderOperator operator = null;
for (Map.Entry<Integer, AbstractOrderOperator> entry: orderOperatorMaps.entrySet()) {
if (event.status == entry.getKey()) {
operator = entry.getValue();
}
}
if (null == operator) {
throw new IllegalArgumentException(String.format("can't find proper operator. The parameter state :%s", event.toString()));
}
return operator;
}
/**
* 根據(jù)入?yún)顟B(tài)枚舉實(shí)例獲取對(duì)應(yīng)的狀態(tài)后處理器
* @param event event
* @return
*/
private AbstractOrderProcessor getOrderProcessor(OrderStatusEnum event) {
AbstractOrderProcessor processor = null;
for (Map.Entry<Integer, AbstractOrderProcessor> entry : orderProcessorMaps.entrySet()) {
if (event.status == entry.getKey()) {
processor = entry.getValue();
}
}
if (null == processor) {
throw new IllegalArgumentException(String.format("can't find proper processor. The parameter state :%s", event.toString()));
}
return processor;
}
/**
* 判斷是不是已完成訂單
* @param status 訂單狀態(tài)碼
* @return
*/
private boolean isFinalStatus(int status) {
return OrderStatusEnum.ORDER_FINISHED.status == status;
}
}
核心的代碼就是類中的 handleEvent 方法巷蚪。
對(duì)應(yīng)的獲取到的組件處理類示例:
/**
* @author carpeng.gao@qunar.com
* @date 2018/10/29 17:47
**/
@Data
public abstract class AbstractOrderOperator {
int status;
public abstract int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum);
}
===================================
/**
* 創(chuàng)建訂單操作狀態(tài)流轉(zhuǎn)
* @author carpeng.gao@qunar.com
* @date 2018/10/29 17:50
**/
@Component
@OrderOperator
public class CreateOrderOperator extends AbstractOrderOperator {
public CreateOrderOperator() {
super.setStatus(1);
}
@Override
public int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum) {
if (orderStatus != OrderStatusEnum.CREATE_EVENT.status && orderStatus != OrderStatusEnum.ORDER_CANCEL.status) {
throw new IllegalArgumentException(String.format("create operation can't handle the status: %s", orderStatus));
}
System.out.println("進(jìn)入創(chuàng)建訂單狀態(tài)扭轉(zhuǎn)處理器...");
switch (orderStatusEnum) {
case CREATE_EVENT:
return OrderStatusEnum.FORMAL_EVENT.status;
case ORDER_CANCEL:
return OrderStatusEnum.ORDER_CANCEL.status;
default:
return getStatus();
}
}
}
后處理器
/**
* 訂單處理器
* @author carpeng.gao@qunar.com
* @date 2018/10/31 14:57
**/
@Data
public abstract class AbstractOrderProcessor {
int status;
public abstract boolean process(String orderId, Object... params);
}
================================
/**
* @author carpeng.gao@qunar.com
* @date 2018/10/31 14:59
**/
@Component
@OrderProcessor
public class CreateOrderProcessor extends AbstractOrderProcessor{
public CreateOrderProcessor() {
super.setStatus(1);
}
@Override
public boolean process(String orderId, Object... params) {
// TODO 創(chuàng)建/取消訂單對(duì)應(yīng)的數(shù)據(jù)庫修改病毡,mq發(fā)送等操作,可以在此處process方法中完成
System.out.println("進(jìn)入創(chuàng)建訂單后處理器...");
return true;
}
}
這些組件類都是依賴于spring的組件掃描注入屁柏。如果要定制化地處理自己的組件類啦膜。可以用一些其他的技巧來處理淌喻。比如此處使用到了自定義注解僧家,通過自定義注解+自定義狀態(tài)機(jī)初始化類來完成對(duì)應(yīng)組件類的篩選與初始化。將這個(gè)初始化類加載完畢似嗤,狀態(tài)機(jī)就可以正常使用了啸臀。
/**
* 狀態(tài)機(jī)前置激活類,在spring中掃描配置此類 <br/>
* 使用自定義注解標(biāo)記對(duì)應(yīng)的狀態(tài)處理器和后置處理器并在初始化操作中完成對(duì)應(yīng)處理器的初始化。
* @author carpeng.gao@qunar.com
* @date 2018/10/31 15:30
**/
@Component
public class Initialization implements BeanPostProcessor {
@Resource
OrderStateManager manager;
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractOrderOperator && bean.getClass().isAnnotationPresent(OrderOperator.class) ) {
AbstractOrderOperator orderState = (AbstractOrderOperator) bean;
manager.orderOperatorMaps.put(orderState.getStatus(), orderState);
}
if (bean instanceof AbstractOrderProcessor && bean.getClass().isAnnotationPresent(OrderProcessor.class) ) {
AbstractOrderProcessor orderProcessor = (AbstractOrderProcessor) bean;
manager.orderProcessorMaps.put(orderProcessor.getStatus(), orderProcessor);
}
return bean;
}
}
這里有一個(gè)問題就是在正式開發(fā)環(huán)境中烁落,依賴于項(xiàng)目的spring環(huán)境乘粒,需要在狀態(tài)機(jī)正式運(yùn)行前將對(duì)應(yīng)的狀態(tài)扭轉(zhuǎn)組件類(operator和processor)注入到環(huán)境中。
四 示例
寫完公司的項(xiàng)目后伤塌,自己再動(dòng)手編寫了一個(gè)簡化版的demo灯萍。包含了上述代碼和測試用例。