一. 什么是狀態(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è)方法)
-
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)移。
-
狀態(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; } } - (1) 定義狀態(tài)接口. 方法為対各事件做出的動(dòng)作
- 優(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)。
- 狀態(tài)模式是改進(jìn)上面使用 if-else 描述狀態(tài)轉(zhuǎn)移機(jī)的做法. 將狀態(tài)轉(zhuǎn)移和動(dòng)作執(zhí)行拆分到不同類(lèi)中
-
查表法
- 生成狀態(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; } } - 生成狀態(tài)表