天天看點

設計模式--行為型--狀态模式

狀态模式在實際的軟體開發中并不常用,但在能夠用到它的場景能夠發揮重要作用。狀态模式一般用來實作狀态機,而狀态機常用在遊戲、工作流引擎等系統的開發中。不過狀态台機的實作有多種,比較常用的是分支邏輯法和查表法

狀态模式,英文翻譯State Pattern。允許對象内部狀态發生改變時,對象看起來好像修改了它的類。

主要解決:對象行為依賴于它的狀态(屬性),并且可以根據它的狀态來改變它的相關行為。一般用來實作狀态機

什麼是有限狀态機?

有限狀态機,英文翻譯Finite State Machine,英文縮寫FSM,簡稱為狀态機。狀态機有三個組成部分:狀态(state)、事件(Event)、動作(Action)。其中,事件也稱為轉移條件(Transition Condition)。事件觸發狀态的轉移以及動作的執行。不過,動作不是必須的,也可能隻是轉移狀态,不執行任何動作。

上面給出了狀态機的定義,那結合“超級馬裡奧”這款遊戲來進行說明:

在遊戲中,馬裡奧可有變身為多種形态,比如:小馬裡奧(Small Mario)、超級馬裡奧(Super Mario)、鬥篷馬裡奧(Cape Mario)、火焰馬裡奧(fire Mario)等等,在不同的遊戲情節下,各種形态互相轉換,并且相應的增減積分。比如:初始形态是小馬裡奧,吃了蘑菇之後就會變成超級馬裡奧,并且增加100積分。

實際上,馬裡奧形态的轉變就是一個狀态機。其中,馬裡奧的不同形态就是狀态機中的不同"狀态",遊戲情節(比如吃蘑菇)就是狀态機中的“事件”,加減積分就是狀态機中的“動作”。比如,吃蘑菇這個"事件",會觸發狀态轉移:從小馬裡奧變身為超級馬裡奧,以及觸發“動作”的執行(增加100積分)。

下面對遊戲進行了簡化,隻保留了部分事件和狀态,簡化後的狀态轉移如下圖所示:

設計模式--行為型--狀态模式

如何實作上面的狀态機呢?首先會先給出骨架代碼,然後給出使用if-else分支邏輯法的實作方式,最後會使用狀态模式的方式進行實作。

馬裡奧狀态機的骨架代碼

如下所示:狀态機中包含,吃了蘑菇(obtainMushroom())、獲得鬥篷(obtainCape)、獲得火焰(obtainFireFlower())、遇到怪物(meetMonster())這幾個事件對應的方法。這些方法會根據目前狀态和事件,更新狀态和增減積分。

骨架代碼代碼如下:

/**
 * 馬裡奧的狀态枚舉類
 */
public enum MarioState {
    SMALL(0,"小馬裡奧"),
    SUPPER(1,"超級馬裡奧"),
    CAPE(2,"鬥篷馬裡奧"),
    FIRE(3,"火焰馬裡奧"),
    ;

    private int state;
    private String remark;

    MarioState(int state, String remark) {
        this.state = state;
        this.remark = remark;
    }

    public int getState() {
        return state;
    }

    public String getRemark() {
        return remark;
    }
}

/**
 * 馬裡奧狀态機
 */
public class MairoStateMachine {
    private int score;
    private MarioState currentState;

    public MairoStateMachine() {
        this.score=0;
        currentState=MarioState.SMALL;
    }

    //吃了蘑菇
    public void obtainMushroom(){

    }

    //獲得鬥篷
    public void obtainCape(){
        
    }

    //獲得火焰
    public void obtainFireFlower(){
       
    }

    //碰到小怪物
    public void meetMonster(){
        
    }

    public int getScore() {
        return score;
    }

    public MarioState getCurrentState() {
        return currentState;
    }
}


/**
 * 測試
 */
public class Demo {
    @Test
    public void test() {
        MairoStateMachine mario=new MairoStateMachine();
        mario.obtainMushroom();
        int score=mario.getScore();
        MarioState state=mario.getCurrentState();
        System.out.println("mario score:"+ score +";  state:"+state.getRemark());
    }
}
           

if-else分支邏輯法實作

上面狀态圖中,“動作”這一部分比較簡單。直接參照上面的狀态圖,将每一個狀态轉移和動作的執行直接翻譯為代碼就可以了。這樣編寫的代碼會存在缺陷,存在大量的if-else。

分支邏輯法實作代碼如下:

/**
 * 馬裡奧狀态機
 */
public class MairoStateMachine {
    private int score;
    private MarioState currentState;

    public MairoStateMachine() {
        this.score=0;
        currentState=MarioState.SMALL;
    }

    //吃了蘑菇
    public void obtainMushroom(){
        if(currentState.equals(MarioState.SMALL)){
            this.currentState = MarioState.SUPPER; //狀态更新
            score+=100;  //執行"動作"
        }
    }

    //獲得鬥篷
    public void obtainCape(){
        if(currentState.equals(MarioState.SMALL) || currentState.equals(MarioState.SUPPER)){
            this.currentState = MarioState.CAPE;
            score+=200;
        }
    }

    //獲得火焰
    public void obtainFireFlower(){
        if(currentState.equals(MarioState.SMALL) || currentState.equals(MarioState.SUPPER)){
            this.currentState = MarioState.FIRE;
            score+=300;
        }
    }

    //碰到小怪物
    public void meetMonster(){
        if(currentState.equals(MarioState.SUPPER)){
            this.currentState = MarioState.SMALL;
            score-=100;
        }else if(currentState.equals(MarioState.CAPE)){
            this.currentState = MarioState.SMALL;
            score-=200;
        }else if(currentState.equals(MarioState.FIRE)){
            this.currentState = MarioState.SMALL;
            score-=300;
        }
    }

    public int getScore() {
        return score;
    }

    public MarioState getCurrentState() {
        return currentState;
    }
}
           

如果狀态機中的“動作”簡單,事件也不多的話,這樣實作是完全沒問題的。

狀态模式實作

對于設計模式學習,畫出類圖,可以加深對模式的了解,類圖如下:

設計模式--行為型--狀态模式

其中IMario是狀态的接口,定義了所有事件。SmallMario、SupperMario、CapeMario、FireMario是IMario接口的實作類。分别對應着狀态機中的4個狀态。代碼實作如下。

/**
 * 馬裡奧的狀态接口類
 */
public interface IMario {
    MarioState getName();
    void obtainMushroom();
    void obtainCape();
    void obtainFireFlower();
    void meetMonster();
}

/**
 * 馬裡奧的狀态抽線類
 *
 * 用于具體的狀态類隻重寫自己所擁有的事件,屏蔽其沒有擁有的事件
 */
public abstract class AbstraceMario implements IMario {
    protected MarioStateMachine marioStateMachine;

    public AbstraceMario(MarioStateMachine marioStateMachine) {
        this.marioStateMachine = marioStateMachine;
    }

    @Override
    public MarioState getName() {return null;}

    @Override
    public void obtainMushroom() {}

    @Override
    public void obtainCape() {}

    @Override
    public void obtainFireFlower() {}

    @Override
    public void meetMonster() {}
}

/**
 * 小馬裡奧
 *
 * 用于處理小馬裡奧所用于的全部事件
 */
public class SmallMario extends AbstraceMario {

    public SmallMario(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.SMALL;
    }

    @Override
    public void obtainMushroom() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+100);
    }

    @Override
    public void obtainCape() {
        marioStateMachine.setCurrentState(new CapeMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+200);
    }

    @Override
    public void obtainFireFlower() {
        marioStateMachine.setCurrentState(new FireMairo(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+3300);
    }
}

/**
 * 超級馬裡奧
 */
public class SupperMario extends AbstraceMario {
    public SupperMario(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.SUPPER;
    }

    @Override
    public void obtainCape() {
        marioStateMachine.setCurrentState(new CapeMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+200);
    }

    @Override
    public void obtainFireFlower() {
        marioStateMachine.setCurrentState(new FireMairo(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+300);
    }

    @Override
    public void meetMonster() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()-100);
    }
}

/**
 * 鬥篷馬裡奧
 */
public class CapeMario extends AbstraceMario {
    public CapeMario(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.CAPE;
    }

    @Override
    public void meetMonster() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()-100);
    }
}

/**
 * 火焰馬裡奧
 */
public class FireMairo extends AbstraceMario {
    public FireMairo(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.FIRE;
    }

    @Override
    public void meetMonster() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()-100);
    }
}
           
/**
 * 馬裡奧狀态機
 */
public class MarioStateMachine {
    private int score;
    private IMario currentState;  //不在使用枚舉來表示

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = new SmallMario(this);
    }

    //吃了蘑菇
    public void obtainMushroom(){
        this.currentState.obtainMushroom();
    }

    //獲得鬥篷
    public void obtainCape(){
        this.currentState.obtainCape();
    }

    //獲得火焰
    public void obtainFireFlower(){
        this.currentState.obtainFireFlower();
    }

    //碰到小怪物
    public void meetMonster(){
        this.currentState.meetMonster();
    }
    public int getScore() {
        return score;
    }

    public IMario getCurrentState() {
        return currentState;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public void setCurrentState(IMario currentState) {
        this.currentState = currentState;
    }
}
           

測試代碼:

@Test
    public void test(){
        MarioStateMachine stateMachine = new MarioStateMachine();
        stateMachine.obtainMushroom();
        stateMachine.obtainFireFlower();
        int score=stateMachine.getScore();
        IMario state=stateMachine.getCurrentState();
        System.out.println("mario score:"+ score +";  state:"+state.getName().getRemark());
    }
           

執行結果:

設計模式--行為型--狀态模式

從上面代碼可以看出,原來所有的狀态轉移和動作代碼執行邏輯,都集中在MarioStateMachine 。現在這些代碼邏輯被分散到了各個狀态類中。。

這裡有一點需要關注,MarioStateMachine和各個狀态類之間是雙向依賴關系。MarioStateMachine依賴狀态類是理所當然的,而反過來,狀态類為什麼要依賴MarioStateMachine呢?因為各個狀态類需要更新MarioStateMachine中的score 和 currentState。

總結:

狀态模式:需要定義一個狀态接口,包含了狀态機中的所有事件,有多少個狀态,就有多少個狀态類。狀态類中的事件需要完成兩件事,一件是業務邏輯的實作,一件是狀态的轉移。

如果業務邏輯簡單,比較推薦使用if-else分支。

如果業務邏輯比較複雜,那就推薦使用狀态機來實作。

參考:設計模式之美--王争