狀态模式在實際的軟體開發中并不常用,但在能夠用到它的場景能夠發揮重要作用。狀态模式一般用來實作狀态機,而狀态機常用在遊戲、工作流引擎等系統的開發中。不過狀态台機的實作有多種,比較常用的是分支邏輯法和查表法
狀态模式,英文翻譯State Pattern。允許對象内部狀态發生改變時,對象看起來好像修改了它的類。
主要解決:對象行為依賴于它的狀态(屬性),并且可以根據它的狀态來改變它的相關行為。一般用來實作狀态機
什麼是有限狀态機?
有限狀态機,英文翻譯Finite State Machine,英文縮寫FSM,簡稱為狀态機。狀态機有三個組成部分:狀态(state)、事件(Event)、動作(Action)。其中,事件也稱為轉移條件(Transition Condition)。事件觸發狀态的轉移以及動作的執行。不過,動作不是必須的,也可能隻是轉移狀态,不執行任何動作。
上面給出了狀态機的定義,那結合“超級馬裡奧”這款遊戲來進行說明:
在遊戲中,馬裡奧可有變身為多種形态,比如:小馬裡奧(Small Mario)、超級馬裡奧(Super Mario)、鬥篷馬裡奧(Cape Mario)、火焰馬裡奧(fire Mario)等等,在不同的遊戲情節下,各種形态互相轉換,并且相應的增減積分。比如:初始形态是小馬裡奧,吃了蘑菇之後就會變成超級馬裡奧,并且增加100積分。
實際上,馬裡奧形态的轉變就是一個狀态機。其中,馬裡奧的不同形态就是狀态機中的不同"狀态",遊戲情節(比如吃蘑菇)就是狀态機中的“事件”,加減積分就是狀态機中的“動作”。比如,吃蘑菇這個"事件",會觸發狀态轉移:從小馬裡奧變身為超級馬裡奧,以及觸發“動作”的執行(增加100積分)。
下面對遊戲進行了簡化,隻保留了部分事件和狀态,簡化後的狀态轉移如下圖所示:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL0MTMwEDN0EjM3IzNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
如何實作上面的狀态機呢?首先會先給出骨架代碼,然後給出使用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分支。
如果業務邏輯比較複雜,那就推薦使用狀态機來實作。
參考:設計模式之美--王争