1. 前言
狀態(tài)模式一般是用在對(duì)象內(nèi)部的狀態(tài)流轉(zhuǎn)場(chǎng)景中,用來實(shí)現(xiàn)狀態(tài)機(jī)。
什么是狀態(tài)機(jī)呢?
狀態(tài)機(jī)是對(duì)狀態(tài)轉(zhuǎn)移的抽象,由事件、狀態(tài)、動(dòng)作組成,事件有時(shí)候也被稱為轉(zhuǎn)移事件或者轉(zhuǎn)移,當(dāng)事件觸發(fā)時(shí),可以將狀態(tài)由一個(gè)狀態(tài)變更為另一個(gè)狀態(tài),并執(zhí)行動(dòng)作。其中,事件和狀態(tài)是必須存在的,動(dòng)作可以不要。
下面是一張狀態(tài)圖,表達(dá)的就是一個(gè)狀態(tài)機(jī)的模型。
通俗來講,就是對(duì)狀態(tài)的變更做了一定的限制,不能隨意的修改狀態(tài),而是只有處于某個(gè)特定的狀態(tài)時(shí),才能變更到另一個(gè)特定的狀態(tài)。
2.狀態(tài)模式
狀態(tài)模式將狀態(tài)抽象成一個(gè)個(gè)的狀態(tài)對(duì)象,狀態(tài)機(jī)當(dāng)前持有某個(gè)狀態(tài)對(duì)象,就表示當(dāng)前的狀態(tài)機(jī)處于什么狀態(tài)。
然后將事件處理為一個(gè)個(gè)的方法,每個(gè)方法中會(huì)操作狀態(tài)機(jī)修改狀態(tài),有需要的情況下,在修改狀態(tài)的同時(shí)還可以執(zhí)行某些動(dòng)作。
把通用部分提取出來后,可以得到這樣一個(gè)通用類圖:
可以看到上面的StateMachine
和State
關(guān)系是雙向的,這是因?yàn)?strong>狀態(tài)機(jī)需要持有狀態(tài)對(duì)象來表示當(dāng)前狀態(tài),以及通過當(dāng)前的狀態(tài)對(duì)象中的方法進(jìn)行狀態(tài)的流轉(zhuǎn),而流轉(zhuǎn)的結(jié)果需要重新set
到狀態(tài)機(jī)中,又要求State
必須持有狀態(tài)機(jī)對(duì)象。
當(dāng)然,這里State
對(duì)StateMachine
的關(guān)系也可以通過依賴來表示。
2.1.訂單狀態(tài)流轉(zhuǎn)案例
假設(shè)現(xiàn)在有一個(gè)商品訂單的狀態(tài)流轉(zhuǎn)需求,狀態(tài)圖如下:
這里沒有加退款的狀態(tài),后續(xù)的拓展例子上會(huì)加上,用這種方式來體驗(yàn)狀態(tài)模式的拓展性。
我們拿著這個(gè)圖的時(shí)候,可以簡(jiǎn)單的在腦海里面過一遍如果通過if/else
或者switch
來做,應(yīng)該要怎么寫,后續(xù)如果想把退款的狀態(tài)加入進(jìn)去又該怎么拓展,這種方式應(yīng)該大家都會(huì),就不在這里贅述了。
接下來,就一步步的通過狀態(tài)模式來實(shí)現(xiàn)這么一個(gè)狀態(tài)機(jī)。
2.1.1.狀態(tài)枚舉定義
定義狀態(tài)枚舉主要是為了統(tǒng)一狀態(tài)常量,因?yàn)橛唵问切枰鋷斓模覀冊(cè)诔志没綌?shù)據(jù)庫時(shí),不能把狀態(tài)對(duì)象保存進(jìn)去,所以會(huì)涉及到狀態(tài)常量與狀態(tài)對(duì)象的互相轉(zhuǎn)換。定義的枚舉如下:
import lombok.Getter;
@Getter
public enum OrderStateEnum {
WAIT_PAYMENT(1, "待支付"),
WAIT_DELIVER(2, "待發(fā)貨"),
WAIT_RECEIVE(3, "待收貨"),
RECEIVED(4, "已收貨"),
CANCEL(5, "已取消");
private final int state;
private final String desc;
OrderStateEnum(int state, String desc) {
this.state = state;
this.desc = desc;
}
public int getState() {
return state;
}
public String getDesc() {
return desc;
}
}
2.1.2.狀態(tài)接口與實(shí)現(xiàn)
先上代碼:
public interface OrderState {
OrderStateEnum orderStateType();
default void pay(OrderStateMachine stateMachine) {
System.out.println("|--當(dāng)前訂單狀態(tài)不支持支付,已忽略");
}
default void cancel(OrderStateMachine stateMachine) {
System.out.println("|--當(dāng)前訂單狀態(tài)不支持取消,已忽略");
}
default void deliver(OrderStateMachine stateMachine) {
System.out.println("|--當(dāng)前訂單狀態(tài)不支持發(fā)貨,已忽略");
}
default void receive(OrderStateMachine stateMachine) {
System.out.println("|--當(dāng)前訂單狀態(tài)不支持收貨,已忽略");
}
}
接口中定義的pay
,cancel
等方法就是事件,供子類進(jìn)行實(shí)現(xiàn),相信大家也發(fā)現(xiàn)了,這些事件沒有定義成抽象方法,而是通過default
定義成了一個(gè)實(shí)例方法。不太清楚為什么的同學(xué),可以先思考一下為什么要這么定義。
其實(shí)這么定義的好處是各個(gè)狀態(tài)子類只需要實(shí)現(xiàn)自己需要的方法,而不用把所有的方法都實(shí)現(xiàn)一遍,這種做法在Spring
中也比較常見,在JDK8
之前通常是用xxxWrapper
來實(shí)現(xiàn)的,JDK8
之后就重構(gòu)為直接使用default
方法來實(shí)現(xiàn)了。
舉個(gè)例子:后續(xù)如果需要加入退款狀態(tài),接口中也會(huì)新增一個(gè)提交退款的事件,在各個(gè)子類中,選擇需要實(shí)現(xiàn)提交退款事件的狀態(tài)子類進(jìn)行重寫即可,而不需要所有的子類都重寫。
有多少個(gè)狀態(tài),就有多少個(gè)實(shí)現(xiàn)類,并按照上面的狀態(tài)圖,在對(duì)應(yīng)的狀態(tài)中實(shí)現(xiàn)自己需要的事件。
- 待支付狀態(tài):有支付和取消兩種事件
public class WaitPaymentState implements OrderState { @Override public OrderStateEnum orderStateType() { return OrderStateEnum.WAIT_PAYMENT; } @Override public void pay(OrderStateMachine stateMachine) { stateMachine.setCurrentState(new WaitDeliverState()); } @Override public void cancel(OrderStateMachine stateMachine) { stateMachine.setCurrentState(new CancelState()); } }
- 待發(fā)貨狀態(tài):有發(fā)貨事件
public class WaitDeliverState implements OrderState { @Override public OrderStateEnum orderStateType() { return OrderStateEnum.WAIT_DELIVER; } @Override public void deliver(OrderStateMachine stateMachine) { stateMachine.setCurrentState(new WaitReceiveState()); } }
- 待收貨狀態(tài):有收貨事件
public class WaitReceiveState implements OrderState { @Override public OrderStateEnum orderStateType() { return OrderStateEnum.WAIT_RECEIVE; } @Override public void receive(OrderStateMachine stateMachine) { stateMachine.setCurrentState(new ReceivedState()); } }
- 已收貨狀態(tài):狀態(tài)結(jié)束點(diǎn),沒有其他事件
public class ReceivedState implements OrderState { @Override public OrderStateEnum orderStateType() { return OrderStateEnum.RECEIVED; } }
- 取消狀態(tài):狀態(tài)結(jié)束點(diǎn),沒有其他事件
public class CancelState implements OrderState { @Override public OrderStateEnum orderStateType() { return OrderStateEnum.CANCEL; } }
2.1.3.狀態(tài)機(jī)
狀態(tài)機(jī)中需要持有當(dāng)前狀態(tài)對(duì)象,同時(shí)需要把狀態(tài)接口中的事件同步定義到狀態(tài)機(jī)中,以便外部業(yè)務(wù)對(duì)象調(diào)用。
除此之外,狀態(tài)枚舉常量與狀態(tài)對(duì)象之間的映射關(guān)系也可以直接配置在當(dāng)前狀態(tài)機(jī)中,功能更加內(nèi)聚。
public class OrderStateMachine {
public static final Map<OrderStateEnum, OrderState> ORDER_STATE_MAP = new HashMap<>();
static {
ORDER_STATE_MAP.put(OrderStateEnum.WAIT_PAYMENT, new WaitPaymentState());
ORDER_STATE_MAP.put(OrderStateEnum.WAIT_DELIVER, new WaitDeliverState());
ORDER_STATE_MAP.put(OrderStateEnum.WAIT_RECEIVE, new WaitReceiveState());
ORDER_STATE_MAP.put(OrderStateEnum.RECEIVED, new ReceivedState());
ORDER_STATE_MAP.put(OrderStateEnum.CANCEL, new CancelState());
}
private OrderState currentState;
public OrderStateMachine(OrderStateEnum orderStateEnum) {
this.currentState = ORDER_STATE_MAP.get(orderStateEnum);
}
public OrderState getCurrentState() {
return currentState;
}
public void setCurrentState(OrderState currentState) {
this.currentState = currentState;
}
void pay() {
currentState.pay(this);
}
void deliver() {
currentState.deliver(this);
}
void receive() {
currentState.receive(this);
}
void cancel() {
currentState.cancel(this);
}
}
2.1.4.測(cè)試
做一下狀態(tài)機(jī)的測(cè)試,由于打印的日志重復(fù)度很高,這里取了個(gè)巧,將函數(shù)作為參數(shù)封裝了一下:
public class OrderService {
public static void main(String[] args) {
OrderStateMachine stateMachine = new OrderStateMachine(OrderStateEnum.WAIT_DELIVER);
invoke(stateMachine::pay, "用戶支付", stateMachine);
invoke(stateMachine::deliver, "商家發(fā)貨", stateMachine);
invoke(stateMachine::receive, "用戶收貨", stateMachine);
invoke(stateMachine::cancel, "取消支付", stateMachine);
}
public static void invoke(Runnable runnable, String desc, OrderStateMachine stateMachine) {
System.out.println(desc + "前訂單狀態(tài): " + stateMachine.getCurrentState().orderStateType().getDesc());
runnable.run();
System.out.println(desc + "后訂單狀態(tài): " + stateMachine.getCurrentState().orderStateType().getDesc());
System.out.println("------------------");
}
}
以待發(fā)貨作為狀態(tài)常量創(chuàng)建了一個(gè)狀態(tài)機(jī),狀態(tài)機(jī)當(dāng)前的狀態(tài)就是待發(fā)貨,下面的四個(gè)調(diào)用中,第1,4個(gè)是不會(huì)改變狀態(tài)的,第2,3個(gè)會(huì)改變狀態(tài),下面以執(zhí)行結(jié)果來驗(yàn)證猜測(cè):
用戶支付前訂單狀態(tài): 待發(fā)貨
|--當(dāng)前訂單狀態(tài)不支持支付,已忽略
用戶支付后訂單狀態(tài): 待發(fā)貨
------------------
商家發(fā)貨前訂單狀態(tài): 待發(fā)貨
商家發(fā)貨后訂單狀態(tài): 待收貨
------------------
用戶收貨前訂單狀態(tài): 待收貨
用戶收貨后訂單狀態(tài): 已收貨
------------------
取消支付前訂單狀態(tài): 已收貨
|--當(dāng)前訂單狀態(tài)不支持取消,已忽略
取消支付后訂單狀態(tài): 已收貨
------------------
2.2.退款狀態(tài)的拓展
通過狀態(tài)模式來實(shí)現(xiàn)狀態(tài)機(jī),看重的就是它帶來的拓展性和易維護(hù)性,所以在原有的基礎(chǔ)上,加上退款的事件和狀態(tài),一起看看需要做些什么事。
2.2.1.代碼拓展
下面是加入了退款的狀態(tài)圖:
通過狀態(tài)圖可以看到,需要加入:
- 兩個(gè)狀態(tài):退款中和已退款
- 兩個(gè)事件:申請(qǐng)退款和確認(rèn)退款
- 原有狀態(tài)拓展:待發(fā)貨、待收貨、已收貨 3個(gè)狀態(tài)中都需要引入申請(qǐng)退款事件
綜上,一步一步的拓展代碼:
- 第一步:拓展枚舉常量
public enum OrderStateEnum { WAIT_PAYMENT(1, "待支付"), WAIT_DELIVER(2, "待發(fā)貨"), WAIT_RECEIVE(3, "待收貨"), RECEIVED(4, "已收貨"), CANCEL(5, "已取消"), REFUNDING(6, "退款中"), REFUNDED(7, "已退款"), ; // 省略后續(xù)代碼…… }
- 第二步:拓展?fàn)顟B(tài)接口
public interface OrderState { // 省略已有代碼…… default void refund(OrderStateMachine stateMachine) { System.out.println("|--當(dāng)前訂單狀態(tài)不支持退款,已忽略"); } default void confirmRefund(OrderStateMachine stateMachine) { System.out.println("當(dāng)前訂單狀態(tài)不支持確認(rèn)退款,已忽略"); } }
- 第三步:新增兩個(gè)狀態(tài),退款中與已退款
public class RefundingState implements OrderState {
@Override
public OrderStateEnum name() {
return OrderStateEnum.REFUNDING;
}
@Override
public void confirmRefund(OrderStateMachine stateMachine) {
stateMachine.setCurrentState(new RefundedState());
}
}
public class RefundedState implements OrderState {
@Override
public OrderStateEnum name() {
return OrderStateEnum.REFUNDED;
}
}
- 第四步:拓展原有狀態(tài),待發(fā)貨,待收貨,已收貨
public class WaitDeliverState implements OrderState {
// 省略已有代碼……
@Override
public void refund(OrderStateMachine stateMachine) {
stateMachine.setCurrentState(new RefundingState());
}
}
public class WaitReceiveState implements OrderState {
// 省略已有代碼……
@Override
public void refund(OrderStateMachine stateMachine) {
stateMachine.setCurrentState(new RefundingState());
}
}
public class ReceivedState implements OrderState {
// 省略已有代碼……
@Override
public void refund(OrderStateMachine stateMachine) {
stateMachine.setCurrentState(new RefundingState());
}
}
- 第五步:拓展?fàn)顟B(tài)機(jī)
public class OrderStateMachine {
public static final Map<OrderStateEnum, OrderState> ORDER_STATE_MAP = new HashMap<>();
static {
// 省略已有狀態(tài)……
ORDER_STATE_MAP.put(OrderStateEnum.REFUNDING, new RefundingState());
ORDER_STATE_MAP.put(OrderStateEnum.REFUNDED, new RefundedState());
}
// 省略已有方法……
void refund() {
currentState.refund(this);
}
void confirmRefund() {
currentState.confirmRefund(this);
}
}
2.2.2.測(cè)試
在上面的代碼中可以看到,都是在對(duì)配置進(jìn)行追加,而沒有對(duì)原有的邏輯做任何的修改,然后寫一個(gè)測(cè)試:
public class OrderService {
public static void main(String[] args) {
OrderStateMachine stateMachine = new OrderStateMachine(OrderStateEnum.WAIT_DELIVER);
invoke(stateMachine::pay, "用戶支付", stateMachine);
invoke(stateMachine::deliver, "商家發(fā)貨", stateMachine);
invoke(stateMachine::receive, "用戶收貨", stateMachine);
invoke(stateMachine::cancel, "取消支付", stateMachine);
invoke(stateMachine::refund, "申請(qǐng)退款", stateMachine);
invoke(stateMachine::confirmRefund, "確認(rèn)退款", stateMachine);
}
public static void invoke(Runnable runnable, String desc, OrderStateMachine stateMachine) {
System.out.println(desc + "前訂單狀態(tài): " + stateMachine.getCurrentState().orderStateType().getDesc());
runnable.run();
System.out.println(desc + "后訂單狀態(tài): " + stateMachine.getCurrentState().orderStateType().getDesc());
System.out.println("------------------");
}
}
查看日志,是否觸發(fā)退款:
用戶支付前訂單狀態(tài): 待發(fā)貨
|--當(dāng)前訂單狀態(tài)不支持支付,已忽略
用戶支付后訂單狀態(tài): 待發(fā)貨
------------------
商家發(fā)貨前訂單狀態(tài): 待發(fā)貨
商家發(fā)貨后訂單狀態(tài): 待收貨
------------------
用戶收貨前訂單狀態(tài): 待收貨
用戶收貨后訂單狀態(tài): 已收貨
------------------
取消支付前訂單狀態(tài): 已收貨
|--當(dāng)前訂單狀態(tài)不支持取消,已忽略
取消支付后訂單狀態(tài): 已收貨
------------------
申請(qǐng)退款前訂單狀態(tài): 已收貨
申請(qǐng)退款后訂單狀態(tài): 退款中
------------------
確認(rèn)退款前訂單狀態(tài): 退款中
確認(rèn)退款后訂單狀態(tài): 已退款
------------------
2.3.小結(jié)
從上面的代碼可以看到,通過狀態(tài)模式可以很輕松的對(duì)狀態(tài)進(jìn)行拓展。
不過上面的例子中沒有對(duì)狀態(tài)機(jī)中的動(dòng)作進(jìn)行實(shí)現(xiàn),其實(shí)動(dòng)作和狀態(tài)轉(zhuǎn)換的邏輯放在一起就可以了,即通過事件(方法調(diào)用) 可以變更狀態(tài),同時(shí)也能夠觸發(fā)對(duì)應(yīng)的動(dòng)作。
此外,代碼中只是狀態(tài)機(jī)的流程,實(shí)際的開發(fā)中應(yīng)該將狀態(tài)機(jī)關(guān)聯(lián)到對(duì)應(yīng)的業(yè)務(wù)實(shí)體中,通過業(yè)務(wù)實(shí)體的實(shí)時(shí)狀態(tài)來創(chuàng)建狀態(tài)機(jī),在完成狀態(tài)流轉(zhuǎn)之后再將狀態(tài)更新到業(yè)務(wù)實(shí)體中。
3.總結(jié)
本篇主要講述了如何通過狀態(tài)模式來實(shí)現(xiàn)一個(gè)狀態(tài)機(jī)。狀態(tài)模式的實(shí)現(xiàn),代碼結(jié)構(gòu)清晰(相對(duì)于if/else
,switch
)拓展性強(qiáng),同時(shí)也起到了良好的封裝效果(狀態(tài)在狀態(tài)機(jī)內(nèi)部流轉(zhuǎn),業(yè)務(wù)流程不需要關(guān)心狀態(tài)到底是怎么流轉(zhuǎn)的)。
當(dāng)然缺點(diǎn)就是類膨脹問題,類會(huì)比較多,如果狀態(tài)非常復(fù)雜的情況下,也可以采取其他辦法來實(shí)現(xiàn)狀態(tài)機(jī),例如查表法。
總之,要分析并實(shí)現(xiàn)一個(gè)業(yè)務(wù)流程中的狀態(tài)流轉(zhuǎn)的時(shí)候,先畫出狀態(tài)圖,以狀態(tài)圖為指導(dǎo)來選擇狀態(tài)機(jī)的實(shí)現(xiàn)方式即可,在狀態(tài)相對(duì)不那么復(fù)雜的情況下,可以優(yōu)先考慮使用狀態(tài)模式。文章來源:http://www.zghlxwxcb.cn/news/detail-657998.html
附:《【UML建?!繝顟B(tài)圖(State Machine Diagram)》文章來源地址http://www.zghlxwxcb.cn/news/detail-657998.html
到了這里,關(guān)于【設(shè)計(jì)模式】訂單狀態(tài)流傳中的狀態(tài)機(jī)與狀態(tài)模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!