在實際的軟件開發中,狀態模式不是很常用,但在一些能夠用到的場景里,能發揮非常大的作用。
狀態模式一般用於實現狀態機,而狀態機一般用在游戲、工作流引擎等軟件開發中。
狀態機有三個組成部分:狀態、事件和動作。觸發某個事件可以改變對象的狀態。
以超級馬里奧這個小游戲為例,一進入游戲是個小馬里奧,吃到蘑菇就會變成超級馬里奧,並增加相應積分,碰到怪獸又會變回小馬里奧。減去積分。
獲得火焰會變成火焰馬里奧,獲得斗篷會變成斗篷馬里奧。
吃蘑菇等就是事件,小馬里奧、超級馬里奧、火焰馬里奧就是狀態,增加減少積分就是動作。
當狀態轉換邏輯比較簡單的時候,完全可以在一個類中通過if/else把這些邏輯描述出來。
但當游戲很復雜,狀態很多的時候,上面的代碼的可讀性和可維護性就會變得很差。這個時候就需要引入狀態模式。
一、狀態和事件
首先,我們通過一個枚舉類存儲所有狀態
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; } }
然后定義一個馬里奧接口,用來表示馬里奧的所有事件
/** * 狀態的接口,定義了所有事件,它的4個子類定義了狀態機的所有狀態 */ public interface IMario { State getName(); void obtainMushRoom(MarioStateMachine stateMachine);//吃到蘑菇 void obtainCape(MarioStateMachine stateMachine);//獲得斗篷 void obtainFireFlower(MarioStateMachine stateMachine);//獲得火焰 void meetMonster(MarioStateMachine stateMachine);//遇到怪物 }
二、狀態機類
狀態機類存儲了馬里奧的當前積分和狀態,是核心邏輯類,但因為我們應用了狀態模式。所以具體每個狀態的邏輯都被分配到具體的狀態類中。
這樣狀態機類就會變得很輕量小巧。
/** * 原來的狀態邏輯都集中在本類,采用if/else分支邏輯比較復雜,現在已經分散到四個狀態類中 * 狀態機類和四個狀態類是雙向依賴關系,狀態變化的邏輯通過調用狀態子類完成實現 */ 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; } }
三、具體狀態機類
在狀態機中狀態是不斷切換的,但每個狀態本身並不變化,所以我們將每個狀態子類設計為單例類。
3.1.小馬里奧
/** * 狀態機是不斷變化的,但狀態子類本身是不變的,也沒有成員變量,所有設置成單例更為合適 * 設置成單例之后,狀態子類就不能在持有狀態機變量,但又要對狀態機進行修改,可以把狀態機作為狀態轉化的參數 */ 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... } } }
3.2.超級馬里奧
public class SuperMario implements IMario { private static final SuperMario instance = new SuperMario(); private SuperMario() { } public static SuperMario getInstance() { return instance; } @Override public State getName() { return State.SUPER; } @Override public void obtainMushRoom(MarioStateMachine stateMachine) { } @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) { stateMachine.setCurrentState(SmallMario.getInstance()); stateMachine.setScore(stateMachine.getScore() - 100); } }
3.3.火焰馬里奧
public class FireMario implements IMario { private static final FireMario instance = new FireMario(); private FireMario() { } public static FireMario getInstance() { return instance; } @Override public State getName() { return State.FIRE; } @Override public void obtainMushRoom(MarioStateMachine stateMachine) { } @Override public void obtainCape(MarioStateMachine stateMachine) { } @Override public void obtainFireFlower(MarioStateMachine stateMachine) { } @Override public void meetMonster(MarioStateMachine stateMachine) { stateMachine.setCurrentState(SmallMario.getInstance()); stateMachine.setScore(stateMachine.getScore() - 300); } }
3.4.斗篷馬里奧
public class CapeMario implements IMario { private static final CapeMario instance = new CapeMario(); private CapeMario() { } public static CapeMario getInstance() { return instance; } @Override public State getName() { return State.CAPE; } @Override public void obtainMushRoom(MarioStateMachine stateMachine) { } @Override public void obtainCape(MarioStateMachine stateMachine) { } @Override public void obtainFireFlower(MarioStateMachine stateMachine) { } @Override public void meetMonster(MarioStateMachine stateMachine) { stateMachine.setCurrentState(SmallMario.getInstance()); stateMachine.setScore(stateMachine.getScore() - 200); } }
四、開始游戲
這樣設計,即便是以后再添加新的動作或狀態,我們只需要添加新的狀態類和方法,而不需要改變原因代碼。
真正實現了對擴展開放,都修改關閉。
public class Application { public static void main(String[] args) { //初始化 System.out.println("===========游戲開始==========="); MarioStateMachine mario = new MarioStateMachine(); State currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //吃蘑菇 System.out.println("===========吃蘑菇==========="); mario.obtainMushRoom(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //獲得斗篷 System.out.println("============獲得斗篷=========="); mario.obtainCape(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //遇到怪物 System.out.println("===========遇到怪物==========="); mario.meetMonster(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //獲得火焰 System.out.println("============獲得火焰=========="); mario.obtainFireFlower(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); } }
輸出:
===========游戲開始=========== SMALL 0 ===========吃蘑菇=========== SUPER 100 ============獲得斗篷========== CAPE 300 ===========遇到怪物=========== SMALL 100 ============獲得火焰========== FIRE 400