狀態(tài)模式

一. 什么是狀態(tài)模式

狀態(tài)模式是狀態(tài)機(jī)的一種實(shí)現(xiàn)方式. 狀態(tài)機(jī)又叫有限狀態(tài)機(jī)(FSM)

  • 狀態(tài)模式不常用, 有點(diǎn)像組合模式
  • 狀態(tài)機(jī)包含3個(gè)部分:
    • 狀態(tài)
    • 事件
    • 動(dòng)作

二. 描述 FSM 的是那種方法

背景: 在超級(jí)馬里奧游戲中,馬里奧可以變身為多種形態(tài),比如小馬里奧, 超級(jí)馬里奧, 火焰馬里奧, 斗篷馬里奧等等。在不同的游戲情節(jié)下,各個(gè)形態(tài)會(huì)互相轉(zhuǎn)化,并相應(yīng)的增減積分。比如,初始形態(tài)是小馬里奧,吃了蘑菇之后就會(huì)變成超級(jí)馬里奧,并且增加 100 積分。
馬里奧形態(tài)的轉(zhuǎn)變就是一個(gè)狀態(tài)機(jī)。其中,馬里奧的不同形態(tài)就是狀態(tài)機(jī)中的“狀態(tài)”,游戲情節(jié)(比如吃了蘑菇)就是狀態(tài)機(jī)中的“事件”,加減積分就是狀態(tài)機(jī)中的“動(dòng)作”。比如,吃蘑菇這個(gè)事件,會(huì)觸發(fā)狀態(tài)的轉(zhuǎn)移:從小馬里奧轉(zhuǎn)移到超級(jí)馬里奧,以及觸發(fā)動(dòng)作的執(zhí)行(增加 100 積分)。
如何編程來(lái)實(shí)現(xiàn)上面的狀態(tài)機(jī)呢?(描述狀態(tài)轉(zhuǎn)的3個(gè)方法)

  1. if-else 分支判斷, 根據(jù)不同狀態(tài)做出對(duì)事件的反應(yīng)

    • 分支描述速馬里奧
    // 描述狀態(tài)的枚舉
    public enum State {
      SMALL(0),
      SUPER(1),
      FIRE(2),
      CAPE(3);
    
      private int value;
    
      private State(int value) {
        this.value = value;
      }
    
      public int getValue() {
        return this.value;
      }
    }
    
    // 狀態(tài)機(jī)描述 (遇到事件轉(zhuǎn)變不同狀態(tài))
    public class MarioStateMachine {
      private int score;    // 得分 
      private State currentState;       // 當(dāng)前狀態(tài)
    
      public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;   // 初始狀態(tài)小瑪麗奧
      }
    
      // 吃了蘑菇
      public void obtainMushRoom() {
        if (currentState.equals(State.SMALL)) {
          this.currentState = State.SUPER;
          this.score += 100;
        }
      }
    
      // 獲得斗篷
      public void obtainCape() {
        if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER) ) {
          this.currentState = State.CAPE;
          this.score += 200;
        }
      }
    
      // 吃了著火的花
      public void obtainFireFlower() {
        if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER) ) {
          this.currentState = State.FIRE;
          this.score += 300;
        }
      }
    
      // 碰到怪獸
      public void meetMonster() {
        if (currentState.equals(State.SUPER)) {
          this.currentState = State.SMALL;
          this.score -= 100;
          return;
        }
    
        if (currentState.equals(State.CAPE)) {
          this.currentState = State.SMALL;
          this.score -= 200;
          return;
        }
    
        if (currentState.equals(State.FIRE)) {
          this.currentState = State.SMALL;
          this.score -= 300;
          return;
        }
      }
    
      public int getScore() {
        return this.score;
      }
    
      public State getCurrentState() {
        return this.currentState;
      }
    }
    
    // 調(diào)用方
    public class ApplicationDemo {
      public static void main(String[] args) {
        MarioStateMachine mario = new MarioStateMachine();
        mario.obtainMushRoom();
        int score = mario.getScore();
        State state = mario.getCurrentState();
        System.out.println("mario score: " + score + "; state: " + state);
      }
    }
    
    • 缺點(diǎn)
      對(duì)于簡(jiǎn)單的狀態(tài)機(jī)來(lái)說(shuō),分支邏輯這種實(shí)現(xiàn)方式是可以接受的。但是,對(duì)于復(fù)雜的狀態(tài)機(jī)來(lái)說(shuō),這種實(shí)現(xiàn)方式極易漏寫(xiě)或者錯(cuò)寫(xiě)某個(gè)狀態(tài)轉(zhuǎn)移。
  2. 狀態(tài)模式:

    • 狀態(tài)模式是改進(jìn)上面使用 if-else 描述狀態(tài)轉(zhuǎn)移機(jī)的做法. 將狀態(tài)轉(zhuǎn)移和動(dòng)作執(zhí)行拆分到不同類(lèi)中
      • (1) 定義狀態(tài)接口. 方法為対各事件做出的動(dòng)作
        這里將動(dòng)作設(shè)為單例的, 因?yàn)闋顟B(tài)不包含任何參數(shù)
      // 狀態(tài)接口
      public interface IMario {
        State getName();
        // 持有 MarioStateMachine 作為參數(shù), 是因?yàn)橐摫?狀態(tài)機(jī)中的得分和下一狀態(tài)
        void obtainMushRoom(MarioStateMachine stateMachine);  // 吃了蘑菇
        void obtainCape(MarioStateMachine stateMachine);      // 帶了斗篷
        void obtainFireFlower(MarioStateMachine stateMachine);// 獲得火花
        void meetMonster(MarioStateMachine stateMachine);     // 碰到怪獸
      }
      
      public class SmallMario implements IMario {
        private static final SmallMario instance = new SmallMario();
        private SmallMario() {}
        public static SmallMario getInstance() {
          return instance;
        }
      
        @Override
        public State getName() {
          return State.SMALL;
        }
      
        @Override
        public void obtainMushRoom(MarioStateMachine stateMachine) {
          stateMachine.setCurrentState(SuperMario.getInstance());
          stateMachine.setScore(stateMachine.getScore() + 100);
        }
      
        @Override
        public void obtainCape(MarioStateMachine stateMachine) {
          stateMachine.setCurrentState(CapeMario.getInstance());
          stateMachine.setScore(stateMachine.getScore() + 200);
        }
      
        @Override
        public void obtainFireFlower(MarioStateMachine stateMachine) {
          stateMachine.setCurrentState(FireMario.getInstance());
          stateMachine.setScore(stateMachine.getScore() + 300);
        }
      
        @Override
        public void meetMonster(MarioStateMachine stateMachine) {
          // do nothing...
        }
      }
      
      // 省略SuperMario、CapeMario、FireMario類(lèi)...
      
      • (2) 定義狀態(tài)機(jī): 狀態(tài)機(jī)里有得分和當(dāng)前的狀態(tài)
      public class MarioStateMachine {
        private int score;
        private IMario currentState;
      
        public MarioStateMachine() {
          this.score = 0;
          this.currentState = SmallMario.getInstance();
        }
      
        public void obtainMushRoom() {
          this.currentState.obtainMushRoom(this);
        }
      
        public void obtainCape() {
          this.currentState.obtainCape(this);
        }
      
        public void obtainFireFlower() {
          this.currentState.obtainFireFlower(this);
        }
      
        public void meetMonster() {
          this.currentState.meetMonster(this);
        }
      
        public int getScore() {
          return this.score;
        }
      
        public State getCurrentState() {
          return this.currentState.getName();
        }
      
        public void setScore(int score) {
          this.score = score;
        }
      
        public void setCurrentState(IMario currentState) {
          this.currentState = currentState;
        }
      }
      
    • 優(yōu)點(diǎn)和缺點(diǎn)
      • 實(shí)際上,像游戲這種比較復(fù)雜的狀態(tài)機(jī),包含的狀態(tài)比較多,我優(yōu)先推薦使用查表法,而狀態(tài)模式會(huì)引入非常多的狀態(tài)類(lèi),會(huì)導(dǎo)致代碼比較難維護(hù)。
      • 相反,像電商下單、外賣(mài)下單這種類(lèi)型的狀態(tài)機(jī),它們的狀態(tài)并不多,狀態(tài)轉(zhuǎn)移也比較簡(jiǎn)單,但事件觸發(fā)執(zhí)行的動(dòng)作包含的業(yè)務(wù)邏輯可能會(huì)比較復(fù)雜,所以,更加推薦使用狀態(tài)模式來(lái)實(shí)現(xiàn)。
  3. 查表法

    • 生成狀態(tài)表
      狀態(tài)機(jī)還可以用二維表來(lái)表示,如下所示。在這個(gè)二維表中,第一維表示當(dāng)前狀態(tài),第二維表示事件,值表示當(dāng)前狀態(tài)經(jīng)過(guò)事件之后,轉(zhuǎn)移到的新?tīng)顟B(tài)及其執(zhí)行的動(dòng)作。
    E1(get mushroom) E2(get cape) E3(get fireflower) E4(meet monster)
    小瑪麗奧(small) super/+100 cape/+200 fire/+300 /
    超級(jí)瑪麗奧(super) / cape/+200 fire/+300 small/-100
    斗篷馬里奧(cape) / / / small/-200
    火焰馬里奧(fire) / / / small/-300
    • 查表法描述馬里奧
    // 狀態(tài)枚舉
    public enum Event {
      GOT_MUSHROOM(0),
      GOT_CAPE(1),
      GOT_FIRE(2),
      MET_MONSTER(3);
    
      private int value;
    
      private Event(int value) {
        this.value = value;
      }
    
      public int getValue() {
        return this.value;
      }
    }
    
    // 查表法狀態(tài)機(jī)
    public class MarioStateMachine {
      private int score;
      private State currentState;
    
      // 查找的狀態(tài)轉(zhuǎn)移表
      private static final State[][] transitionTable = {
              {SUPER, CAPE, FIRE, SMALL},
              {SUPER, CAPE, FIRE, SMALL},
              {CAPE, CAPE, CAPE, SMALL},
              {FIRE, FIRE, FIRE, SMALL}
      };
    
      // action 表(獎(jiǎng)勵(lì)或懲罰)
      private static final int[][] actionTable = {
              {+100, +200, +300, +0},
              {+0, +200, +300, -100},
              {+0, +0, +0, -200},
              {+0, +0, +0, -300}
      };
    
      public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;
      }
    
      public void obtainMushRoom() {
        executeEvent(Event.GOT_MUSHROOM);
      }
    
      public void obtainCape() {
        executeEvent(Event.GOT_CAPE);
      }
    
      public void obtainFireFlower() {
        executeEvent(Event.GOT_FIRE);
      }
    
      public void meetMonster() {
        executeEvent(Event.MET_MONSTER);
      }
    
      private void executeEvent(Event event) {
        int stateValue = currentState.getValue();
        int eventValue = event.getValue();
        this.currentState = transitionTable[stateValue][eventValue];
        this.score += actionTable[stateValue][eventValue];
      }
    
      public int getScore() {
        return this.score;
      }
    
      public State getCurrentState() {
        return this.currentState;
      }
    
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容