<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          還有誰不會如此優(yōu)雅的狀態(tài)機模式?

          共 17420字,需瀏覽 35分鐘

           ·

          2024-07-19 08:16

          前言

          狀態(tài)模式一般是用在對象內(nèi)部的狀態(tài)流轉(zhuǎn)場景中,用來實現(xiàn)狀態(tài)機。

          什么是狀態(tài)機?

          狀態(tài)機是對狀態(tài)轉(zhuǎn)移的抽象,由事件、狀態(tài)、動作組成,事件有時候也被稱為轉(zhuǎn)移事件或者轉(zhuǎn)移,當事件觸發(fā)時,可以將狀態(tài)由一個狀態(tài)變更為另一個狀態(tài),并執(zhí)行動作。其中,事件和狀態(tài)是必須存在的,動作可以不要。

          下面是一張狀態(tài)圖,表達的就是一個狀態(tài)機的模型。

          通俗來講,就是對狀態(tài)的變更做了一定的限制,不能隨意的修改狀態(tài),而是只有處于某個特定的狀態(tài)時,才能變更到另一個特定的狀態(tài)。

          狀態(tài)模式

          狀態(tài)模式將狀態(tài)抽象成一個個的狀態(tài)對象,狀態(tài)機當前持有某個狀態(tài)對象,就表示當前的狀態(tài)機處于什么狀態(tài)。然后將事件處理為一個個的方法,每個方法中會操作狀態(tài)機修改狀態(tài),有需要的情況下,在修改狀態(tài)的同時還可以執(zhí)行某些動作。

          把通用部分提取出來后,可以得到這樣一個通用類圖:

          可以看到上面的StateMachine和State關(guān)系是雙向的,這是因為狀態(tài)機需要持有狀態(tài)對象來表示當前狀態(tài),以及通過當前的狀態(tài)對象中的方法進行狀態(tài)的流轉(zhuǎn),而流轉(zhuǎn)的結(jié)果需要重新set到狀態(tài)機中,又要求State必須持有狀態(tài)機對象。當然,這里State對StateMachine的關(guān)系也可以通過依賴來表示。

          訂單狀態(tài)流轉(zhuǎn)案例

          假設(shè)現(xiàn)在有一個商品訂單的狀態(tài)流轉(zhuǎn)需求,狀態(tài)圖如下:

          這里沒有加退款的狀態(tài),后續(xù)的拓展例子上會加上,用這種方式來體驗狀態(tài)模式的拓展性。

          我們拿著這個圖的時候,可以簡單的在腦海里面過一遍如果通過if/else或者switch來做,應(yīng)該要怎么寫,后續(xù)如果想把退款的狀態(tài)加入進去又該怎么拓展,這種方式應(yīng)該大家都會,就不在這里贅述了。

          接下來,就一步步的通過狀態(tài)模式來實現(xiàn)這么一個狀態(tài)機。

          狀態(tài)枚舉定義

          定義狀態(tài)枚舉主要是為了統(tǒng)一狀態(tài)常量,因為訂單是需要落庫的,我們在持久化到數(shù)據(jù)庫時,不能把狀態(tài)對象保存進去,所以會涉及到狀態(tài)常量與狀態(tài)對象的互相轉(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;
              }
          }

          狀態(tài)接口與實現(xiàn)

          先上代碼:

          public interface OrderState {

              OrderStateEnum orderStateType();
              
              default void pay(OrderStateMachine stateMachine) {
                  System.out.println("|--當前訂單狀態(tài)不支持支付,已忽略");
              }
              default void cancel(OrderStateMachine stateMachine) {
                  System.out.println("|--當前訂單狀態(tài)不支持取消,已忽略");
              }
              default void deliver(OrderStateMachine stateMachine) {
                  System.out.println("|--當前訂單狀態(tài)不支持發(fā)貨,已忽略");
              }
              default void receive(OrderStateMachine stateMachine) {
                  System.out.println("|--當前訂單狀態(tài)不支持收貨,已忽略");
              }
          }

          接口中定義的pay,cancel等方法就是事件,供子類進行實現(xiàn),相信大家也發(fā)現(xiàn)了,這些事件沒有定義成抽象方法,而是通過default定義成了一個實例方法。不太清楚為什么的同學,可以先思考一下為什么要這么定義。

          其實這么定義的好處是各個狀態(tài)子類只需要實現(xiàn)自己需要的方法,而不用把所有的方法都實現(xiàn)一遍,這種做法在Spring中也比較常見,在JDK8之前通常是用xxxWrapper來實現(xiàn)的,JDK8之后就重構(gòu)為直接使用default方法來實現(xiàn)了。

          舉個例子:后續(xù)如果需要加入退款狀態(tài),接口中也會新增一個提交退款的事件,在各個子類中,選擇需要實現(xiàn)提交退款事件的狀態(tài)子類進行重寫即可,而不需要所有的子類都重寫。


          有多少個狀態(tài),就有多少個實現(xiàn)類,并按照上面的狀態(tài)圖,在對應(yīng)的狀態(tài)中實現(xiàn)自己需要的事件。

          待支付狀態(tài):有支付和取消兩種事件(注意看事件里面 setCurrentState 做的狀態(tài)流轉(zhuǎn)):

          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é)束點,沒有其他事件

          public class ReceivedState implements OrderState {

              @Override
              public OrderStateEnum orderStateType() {
                  return OrderStateEnum.RECEIVED;
              }

          }

          取消狀態(tài):狀態(tài)結(jié)束點,沒有其他事件

          public class CancelState implements OrderState {

              @Override
              public OrderStateEnum orderStateType() {
                  return OrderStateEnum.CANCEL;
              }
          }

          狀態(tài)機

          狀態(tài)機中需要持有當前狀態(tài)對象,同時需要把狀態(tài)接口中的事件同步定義到狀態(tài)機中,以便外部業(yè)務(wù)對象調(diào)用。

          除此之外,狀態(tài)枚舉常量與狀態(tài)對象之間的映射關(guān)系也可以直接配置在當前狀態(tài)機中,功能更加內(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);
              }

          }

          測試

          做一下狀態(tài)機的測試,由于打印的日志重復度很高,這里取了個巧,將函數(shù)作為參數(shù)封裝了一下:

          public class OrderService {

          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)建了一個狀態(tài)機,狀態(tài)機當前的狀態(tài)就是待發(fā)貨,下面的四個調(diào)用中,第1,4個是不會改變狀態(tài)的,第2,3個會改變狀態(tài),下面以執(zhí)行結(jié)果來驗證猜測:

          用戶支付前訂單狀態(tài): 待發(fā)貨
          |--當前訂單狀態(tài)不支持支付,已忽略
          用戶支付后訂單狀態(tài): 待發(fā)貨
          ------------------
          商家發(fā)貨前訂單狀態(tài): 待發(fā)貨
          商家發(fā)貨后訂單狀態(tài): 待收貨
          ------------------
          用戶收貨前訂單狀態(tài): 待收貨
          用戶收貨后訂單狀態(tài): 已收貨
          ------------------
          取消支付前訂單狀態(tài): 已收貨
          |--當前訂單狀態(tài)不支持取消,已忽略
          取消支付后訂單狀態(tài): 已收貨
          ------------------

          退款狀態(tài)的拓展

          通過狀態(tài)模式來實現(xiàn)狀態(tài)機,看重的就是它帶來的拓展性和易維護性,所以在原有的基礎(chǔ)上,加上退款的事件和狀態(tài),一起看看需要做些什么事。

          代碼拓展

          下面是加入了退款的狀態(tài)圖:

          通過狀態(tài)圖可以看到,需要加入:

          • 兩個狀態(tài):退款中和已退款
          • 兩個事件:申請退款和確認退款
          • 原有狀態(tài)拓展:待發(fā)貨、待收貨、已收貨 3個狀態(tài)中都需要引入申請退款事件

          綜上,一步一步的拓展代碼:

          第一步:拓展枚舉常量

          public enum OrderStateEnum {
              WAIT_PAYMENT(1"待支付"),
              WAIT_DELIVER(2"待發(fā)貨"),
              WAIT_RECEIVE(3"待收貨"),
              RECEIVED(4"已收貨"),
              CANCEL(5"已取消"),
              REFUNDING(6"退款中"),
              REFUNDED(7"已退款"),
              ;
           // 省略后續(xù)代碼……
          }

          第二步:拓展狀態(tài)接口

          public interface OrderState {
              // 省略已有代碼……

              default void refund(OrderStateMachine stateMachine) {
                  System.out.println("|--當前訂單狀態(tài)不支持退款,已忽略");
              }

              default void confirmRefund(OrderStateMachine stateMachine) {
                  System.out.println("當前訂單狀態(tài)不支持確認退款,已忽略");
              }
          }

          第三步:新增兩個狀態(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());
              }

          }

          第五步:拓展狀態(tài)機

          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);
              }
          }

          測試

          在上面的代碼中可以看到,都是在對配置進行追加,而沒有對原有的邏輯做任何的修改,然后寫一個測試:

          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, "申請退款", stateMachine);
                  invoke(stateMachine::confirmRefund, "確認退款", 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ā)貨
          |--當前訂單狀態(tài)不支持支付,已忽略
          用戶支付后訂單狀態(tài): 待發(fā)貨
          ------------------
          商家發(fā)貨前訂單狀態(tài): 待發(fā)貨
          商家發(fā)貨后訂單狀態(tài): 待收貨
          ------------------
          用戶收貨前訂單狀態(tài): 待收貨
          用戶收貨后訂單狀態(tài): 已收貨
          ------------------
          取消支付前訂單狀態(tài): 已收貨
          |--當前訂單狀態(tài)不支持取消,已忽略
          取消支付后訂單狀態(tài): 已收貨
          ------------------
          申請退款前訂單狀態(tài): 已收貨
          申請退款后訂單狀態(tài): 退款中
          ------------------
          確認退款前訂單狀態(tài): 退款中
          確認退款后訂單狀態(tài): 已退款
          ------------------

          小結(jié)

          從上面的代碼可以看到,通過狀態(tài)模式可以很輕松的對狀態(tài)進行拓展。

          不過上面的例子中沒有對狀態(tài)機中的動作進行實現(xiàn),其實動作和狀態(tài)轉(zhuǎn)換的邏輯放在一起就可以了,即通過事件(方法調(diào)用) 可以變更狀態(tài),同時也能夠觸發(fā)對應(yīng)的動作。

          此外,代碼中只是狀態(tài)機的流程,實際的開發(fā)中應(yīng)該將狀態(tài)機關(guān)聯(lián)到對應(yīng)的業(yè)務(wù)實體中,通過業(yè)務(wù)實體的實時狀態(tài)來創(chuàng)建狀態(tài)機,在完成狀態(tài)流轉(zhuǎn)之后再將狀態(tài)更新到業(yè)務(wù)實體中。

          總結(jié)

          本篇主要講述了如何通過狀態(tài)模式來實現(xiàn)一個狀態(tài)機。狀態(tài)模式的實現(xiàn),代碼結(jié)構(gòu)清晰(相對于if/else,switch)拓展性強,同時也起到了良好的封裝效果(狀態(tài)在狀態(tài)機內(nèi)部流轉(zhuǎn),業(yè)務(wù)流程不需要關(guān)心狀態(tài)到底是怎么流轉(zhuǎn)的)。

          當然缺點就是類膨脹問題,類會比較多,如果狀態(tài)非常復雜的情況下,也可以采取其他辦法來實現(xiàn)狀態(tài)機,例如查表法。

          總之,要分析并實現(xiàn)一個業(yè)務(wù)流程中的狀態(tài)流轉(zhuǎn)的時候,先畫出狀態(tài)圖,以狀態(tài)圖為指導來選擇狀態(tài)機的實現(xiàn)方式即可,在狀態(tài)相對不那么復雜的情況下,可以優(yōu)先考慮使用狀態(tài)模式。

          原文來自:https://blog.csdn.net/qq_38249409/article/details/132229581



          面試題庫自研短鏈項目簡歷修改&模擬面試招聘信息
          推薦我的面試小程序編程滿天星,收錄真實大廠面經(jīng),原創(chuàng)高質(zhì)量八股題解,建設(shè)完整知識體系,提供全網(wǎng)唯一小而美的自研短鏈項目教程,一對一簡歷修改 & 模擬面試服務(wù),并定期更新招聘信息,一站式準備大廠面試!
          八股題庫:


          自研短鏈項目:


          簡歷修改:


          模擬面試:



          內(nèi)推信息:

          瀏覽 95
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啪啪啪啪xxxx欧美 | 国产成人无码精品久久久一区 | 国产综合婷婷色 | 国产www在线观看 | 乱伦一级视频 |