java設計模式之狀態模式


在實際的軟件開發中,狀態模式不是很常用,但在一些能夠用到的場景里,能發揮非常大的作用。

狀態模式一般用於實現狀態機,而狀態機一般用在游戲、工作流引擎等軟件開發中。

狀態機有三個組成部分:狀態、事件和動作。觸發某個事件可以改變對象的狀態。

以超級馬里奧這個小游戲為例,一進入游戲是個小馬里奧,吃到蘑菇就會變成超級馬里奧,並增加相應積分,碰到怪獸又會變回小馬里奧。減去積分。

獲得火焰會變成火焰馬里奧,獲得斗篷會變成斗篷馬里奧。

吃蘑菇等就是事件,小馬里奧、超級馬里奧、火焰馬里奧就是狀態,增加減少積分就是動作。

當狀態轉換邏輯比較簡單的時候,完全可以在一個類中通過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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM