有限狀態機,(英語:Finite-state machine,FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。它反映從系統開始到現在時刻的輸入變化,轉移指示狀態變更,並且用必須滿足來確使轉移發生的條件來描述它;動作是在給定時刻要進行的活動的描述。
下面我們透過一個例子來說明。
現在需要你做一個遊戲內的主角,主要有死亡,走,復活三個功能,在沒學狀態機模式之前,你可能會這樣來實現:
現抽象個IHero介面
export enum HeroState { STATE_RUN = 1, STATE_DIE = 2, STATE_STOP = 3}export default interface IHero { run(): void; die(): void; stop(): void;}
Hero 程式碼如下
import IHero, { HeroState } from "./IHero";export class Hero implements IHero { public mCurrentState: HeroState; public run(): void { switch (this.mCurrentState) { case HeroState.STATE_RUN: console.log("curent state is run, do nothing."); break; case HeroState.STATE_DIE: console.log(" curent state is die do noting."); break; case HeroState.STATE_LIFE: this.mCurrentState = HeroState.STATE_RUN; console.log("hero run"); break; default: break; } } public die(): void { switch (this.mCurrentState) { case HeroState.STATE_DIE: console.log(" curent state is die do noting."); break; case HeroState.STATE_LIFE: case HeroState.STATE_RUN: this.mCurrentState = HeroState.STATE_DIE; console.log("hero die"); break; default: break; } } public life(): void { switch (this.mCurrentState) { case HeroState.STATE_DIE: this.mCurrentState = HeroState.STATE_LIFE; console.log("hero life"); break; case HeroState.STATE_LIFE: console.log(" curent state is life do noting."); break; case HeroState.STATE_RUN: console.log(" curent state is run do noting."); break; default: break; } }}
看著還錯喔。
我們都知道,需求總是會改變的,現在你的boss需要在 者復活的時候播放一個動畫特效
那麼程式碼就會變成
export enum HeroState { STATE_RUN = 1, STATE_DIE = 2, STATE_LIFE = 3, STATE_SHOW=4}export default interface IHero { run(): void; die(): void; life(): void; show():void;}
show(): void { switch (this.mCurrentState) { case HeroState.STATE_RUN: console.log("curent state is run, do nothing."); break; case HeroState.STATE_DIE: console.log(" curent state is die do noting."); break; case HeroState.STATE_LIFE: this.mCurrentState = HeroState.STATE_SHOW; console.log("hero run"); break; default: break; } }
真的就完了?終於發現了,run,die,life三個方法中的swtich裡面還需要各多加一個case的判斷,納尼!!!如果以後又增加幾個狀態,那麼還得修改啊,而且隨著狀態的增加,修改的程式碼也會成倍的增加,簡直不可想象。這種情況下,狀態機模式就可以幫你個大忙了。
狀態機模式:允許物件在內部狀態改變時改變它的行為,物件看起來就好像修改了它的類。
是不是還是比較模糊,下面我們直接看程式碼
import HeroHandleState, { HeroState } from "./HeroHandleState";export default abstract class IHero { public abstract request(action: HeroState); public abstract setState(state: HeroHandleState): void; public abstract run(): void; public abstract die(): void; public abstract life(): void;}
import IHero from "../src2/IHero";export enum HeroState { STATE_RUN = 1, STATE_DIE = 2, STATE_LIFE = 3, STATE_SHOW = 4}export default abstract class HeroHandleState { protected iHero: IHero; public constructor(iHero: IHero) { this.iHero = iHero; } public abstract handle(action: HeroState): void;}
import HeroHandleState, { HeroState } from "./HeroHandleState";import { DieHeroHandleState } from "./DieHeroHandleState";export class RunHeroHandleState extends HeroHandleState { public handle(action: HeroState): void { switch (action) { case HeroState.STATE_DIE: this.iHero.die(); this.iHero.setState(new DieHeroHandleState(this.iHero)); break; default: throw new Error(`ERROE ACTION:${action}`) break; } }}
import HeroHandleState, { HeroState } from "./HeroHandleState";import { DieHeroHandleState } from "./DieHeroHandleState";export class LifeHeroHandleState extends HeroHandleState { public handle(action: HeroState): void { switch (action) { case HeroState.STATE_DIE: this.iHero.die(); this.iHero.setState(new DieHeroHandleState(this.iHero)); break; case HeroState.STATE_RUN: this.iHero.life() this.iHero.setState(new LifeHeroHandleState(this.iHero)); break; default: throw new Error(`ERROE ACTION:${action}`) break; } }}
import HeroHandleState, { HeroState } from "./HeroHandleState";import { LifeHeroHandleState } from "./LifeHeroHandleState";export class DieHeroHandleState extends HeroHandleState { public handle(action: HeroState): void { switch (action) { case HeroState.STATE_LIFE: this.iHero.life() this.iHero.setState(new LifeHeroHandleState(this.iHero)); break; default: throw new Error(`ERROE ACTION:${action}`) break; } }}
import IHero from "./IHero";import HeroHandleState, { HeroState } from "./HeroHandleState";import { LifeHeroHandleState } from "./LifeHeroHandleState";export class Hero implements IHero { private state: HeroHandleState = new LifeHeroHandleState(this); public request(action: HeroState) { this.state.handle(action) } public setState(state: HeroHandleState): void { this.state = state; } public run(): void { console.log("run") } public die(): void { console.log("die") } public life(): void { console.log("life") }}
現在的程式碼就簡潔多了,因為Hero只需要實現需要的操作,每次接收輸入的時候(request方法呼叫),只需要交給當前的狀態去處理,而每個狀態不需要知道自己之前的狀態是什麼,只需要知道接收到什麼樣的輸入而做出相應的操作和下一個狀態
這個時候我們我們在增加一個show 狀態
import HeroHandleState, { HeroState } from "./HeroHandleState";import { LifeHeroHandleState } from "./LifeHeroHandleState";export class ShowHeroHandleState extends HeroHandleState { public handle(action: HeroState): void { switch (action) { case HeroState.STATE_LIFE: this.iHero.life() this.iHero.setState(new LifeHeroHandleState(this.iHero)); break; default: throw new Error(`ERROE ACTION:${action}`) break; } }}
現在依然還沒有完事,前面提到,每個狀態不需要知道自己之前的狀態是什麼,只需要知道接收到什麼樣的輸入而做出相應的操作和下一個狀態。
由狀態圖可以看到,HeroState的下一個狀態增加了一個ShowHeroHandleState,所以DieHeroHandleState還需要做一點修改,如下:
import { LifeHeroHandleState } from "./LifeHeroHandleState";export class DieHeroHandleState extends HeroHandleState { public handle(action: HeroState): void { switch (action) { case HeroState.STATE_LIFE: this.iHero.life() this.iHero.setState(new LifeHeroHandleState(this.iHero)); break; case HeroState.STATE_SHOW: this.iHero.show() this.iHero.setState(new ShowHeroHandleState(this.iHero)); break; default: throw new Error(`ERROE ACTION:${action}`) break; } }}
總結:
1.狀態機模式:允許物件在內部狀態改變時改變它的行為,物件看起來就好像修改了它的類(每個狀態可以做出不一樣的動作);
2.擁有多個狀態的物件(Context)只需要實現需要的操作,每次接收輸入的時候(request方法呼叫),只需要交給當前的狀態去處理,而每個狀態不需要知道自己之前的狀態是什麼,只需要知道接收到什麼樣的輸入(或者沒輸入)而做出相應的操作和自己下一個狀態是什麼即可;
3.適當的畫出系統的狀態轉換圖,可以更清晰地實現系統狀態圖。