基於 Kotlin 特性開發的有限狀態機 一. 狀態機 二. 常用的狀態機分類 三. Kotlin 開發的 FSM 四. 應用 五. 總結

一. 狀態機

狀態機是古老的計算機理論,在遊戲開發、嵌入式開發、網絡協議等領域,得到廣泛地使用。

狀態機:它是一個有向圖形,由一組節點和一組相應的轉移函數組成。狀態機通過響應一系列事件而“運行”。每個事件都在屬於“當前” 節點的轉移函數的控制範圍內,其中函數的範圍是節點的一個子集。函數返回“下一個”(也許是同一個)節點。這些節點中至少有一個必須是終態。當到達終態, 狀態機停止。

二. 常用的狀態機分類

FSM

有限狀態機,(英語:Finite-state machine, FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行爲的數學模型。

以下是對狀態機抽象定義

  • State(狀態):構成狀態機的基本單位。 狀態機在任何特定時間都可處於某一狀態。從生命週期來看有Initial State、End State、Suspend State(掛起狀態)
  • Event(事件):導致轉換髮生的事件活動
  • Transitions(轉換器):兩個狀態之間的定向轉換關係,狀態機對發生的特定類型事件響應後當前狀態由A轉換到B。標準轉換、選擇轉、子流程轉換多種抽象實現
  • Actions(轉換操作):在執行某個轉換時執行的具體操作。
  • Guards(檢測器):檢測器出現的原因是爲了轉換操作執行後檢測結果是否滿足特定條件從一個狀態切換到某一個狀態
  • Interceptor(攔截器):對當前狀態改變前、後進行監聽攔截。

DFA

確定有限狀態自動機或確定有限自動機(英語:deterministic finite automaton, DFA)是一個能實現狀態轉移的自動機對於一個給定的屬於該自動機的狀態和一個屬於該自動機字母表的字符,它都能根據事先給定的轉移函數轉移到下一個狀態(這個狀態可以是先前那個狀態)。

DFA 是 FSM 的一種,與 DFA 對應的還有 NFA(非確定性有限自動機)。

DFA 的特性:

  • 沒有衝突:一個狀態對於同樣的輸入,不能有多個規則,即每個輸入只能有一個轉移規則;
  • 沒有遺漏:每個狀態都必須針對每個可能的輸入字符有至少一個規則

以前我寫過的一篇文章《一個快速分析android app使用了哪些sdk的工具》 曾經使用過DFA。

HSM

層次狀態機(英語:Hierarchical State Machine)是狀態機理論中的一種層次結構的模型,各個狀態按照樹狀層次結構組織起來,狀態圖是層次結構的,也就是說每個狀態可以擁有子狀態。

當 FSM 狀態太多的時候,可以將狀態分類,並抽離出來。同類型的狀態做爲一個狀態機,然後再做一個大的狀態機,來維護這些子狀態機。

三. Kotlin 開發的 FSM

github 地址:https://github.com/fengzhizi715/KStateMachine

StateContext

用於保存管理 State 對象實例,表示 State 實例所處的環境。

interface StateContext {

    fun getEvent(): BaseEvent

    fun getSource(): BaseState

    fun getTarget(): BaseState

    fun getException(): Exception?

    fun setException(exception: Exception)

    fun getTransition(): Transition
}

State

構成狀態機的基本單位,狀態機在任何特定時間都可處於某一狀態。

class State(val name: BaseState) {

    private val transitions = hashMapOf<BaseEvent, Transition>() // 存儲當前 State 相關的所有 Transition
    private val stateActions = mutableListOf<StateAction>()  // 當前 State 相關的所有 Action

    /**
     * 當一個 Event 被狀態機系統分發的時候,狀態機用 Action 來進行響應
     * 狀態轉換可以使用 F(S, E) -> (A, S’) 表示
     *
     * @param event: 觸發事件
     * @param targetState: 下一個狀態
     * @param guard: 斷言接口,爲了轉換操作執行後檢測結果是否滿足特定條件從一個狀態切換到某一個狀態
     * @param init
     */
    fun transition(event: BaseEvent, targetState: BaseState, guard: Guard?=null, init: Transition.() -> Unit):State {
        val transition = Transition(event, this.name, targetState, guard)
        transition.init()

        if (transitions.containsKey(event)) { // 同一個 Event 不能對應多個 Transition,即 State 只能通過一個 Event 然後 Transition 到另一個 State
            throw StateMachineException("Adding multiple transitions for the same event is invalid")
        }

        transitions[event] = transition
        return this
    }

    /**
     * State 執行的 Action
     */
    fun action(action: StateAction) {
        stateActions.add(action)
    }

    /**
     * 進入 State 並執行所有的 Action
     */
    fun enter() {
        stateActions.forEach {
            it.invoke(this)
        }
    }

    /**
     * 通過 Event 獲取 Transition
     */
    fun getTransitionForEvent(event: BaseEvent): Transition = transitions[event]?:throw IllegalStateException("Event $event isn't registered with state ${this.name}")

    override fun toString(): String = name.javaClass.simpleName
}

Transition

從一個狀態切換到另一個狀態。

class Transition(private val event: BaseEvent, private val sourceState: BaseState, private val targetState: BaseState, private var guard:Guard?= null) {

    private val actions = mutableListOf<TransitionAction>()

    /**
     * 是否轉換
     * @param context
     */
    fun transit(context: StateContext): Boolean {
        executeTransitionActions(context)
        return context.getException() == null
    }

    /**
     * 執行 Transition 的 Action
     */
    private fun executeTransitionActions(context: StateContext) {

        actions.forEach {
            try {
                it.invoke(this)
            } catch (e:Exception) {
                context.setException(e)
                return
            }
        }
    }

    /**
     * 添加一個 action,在狀態轉換時執行(時間點是在狀態轉換之前)
     */
    fun action(action: TransitionAction) {
        actions.add(action)
    }


    /**
     * 轉換狀態
     */
    fun applyTransition(getNextState: (BaseState) -> State): State = getNextState(targetState)

    /**
     * 設置檢測條件,判斷是否滿足狀態轉換的條件,滿足則執行狀態轉換
     */
    fun guard(guard: Guard) {
        this.guard = guard
    }

    fun getGuard():Guard? = guard

    fun getSourceState(): BaseState = sourceState

    fun getTargetState(): BaseState = targetState

    override fun toString(): String = "${sourceState.javaClass.simpleName} transition to ${targetState.javaClass.simpleName} on ${event.javaClass.simpleName}"
}

狀態機的實現

class StateMachine private constructor(private val initialState: BaseState) {

    private lateinit var currentState: State    // 當前狀態
    private val states = mutableListOf<State>() // 狀態列表
    private val initialized = AtomicBoolean(false) // 是否初始化
    private var globalInterceptor: GlobalInterceptor?=null
    private val transitionCallbacks: MutableList<TransitionCallback> = mutableListOf()

    /**
     * 設置狀態機全局的攔截器,使用時必須要在 initialize() 之前
     * @param event: 狀態機全局的攔截器
     */
    fun interceptor(globalInterceptor: GlobalInterceptor):StateMachine {
        this.globalInterceptor = globalInterceptor
        return this
    }

    /**
     * 初始化狀態機,並進入初始化狀態
     */
    fun initialize() {
        if(initialized.compareAndSet(false, true)){
            currentState = getState(initialState)
            globalInterceptor?.stateEntered(currentState)
            currentState.enter()
        }
    }

    /**
     * 向狀態機添加 State
     */
    fun state(stateName: BaseState, init: State.() -> Unit):StateMachine {
        val state = State(stateName)
        state.init()
        states.add(state)
        return this
    }

    /**
     * 通過狀態名稱獲取狀態
     */
    private fun getState(stateType: BaseState): State = states.firstOrNull { stateType.javaClass == it.name.javaClass } ?: throw NoSuchElementException(stateType.javaClass.canonicalName)

    /**
     * 向狀態機發送 Event,執行狀態轉換
     */
    @Synchronized
    fun sendEvent(e: BaseEvent) {
        try {
            val transition = currentState.getTransitionForEvent(e)

            globalInterceptor?.transitionStarted(transition)

            val stateContext: StateContext = DefaultStateContext(e, transition, transition.getSourceState(), transition.getTargetState())

            //狀態轉換之前執行的 action(Transition 內部的 action), action執行失敗表示不接受事件,返回false
            val accept = transition.transit(stateContext)

            if (!accept) {
                //狀態機發生異常
                globalInterceptor?.stateMachineError(this, StateMachineException("狀態轉換失敗,source ${currentState.name} -> target ${transition.getTargetState()} Event ${e}"))
                return
            }

            val guard = transition.getGuard()?.invoke()?:true

            if (guard) {
                val state = transition.applyTransition { getState(stateContext.getTarget()) }

                val callbacks = transitionCallbacks.toList()

                globalInterceptor?.apply {
                    stateContext(stateContext)
                    transition(transition)
                    stateExited(currentState)
                }

                callbacks.forEach { callback ->
                    callback.enteringState(this, stateContext.getSource(), transition, stateContext.getTarget())
                }

                state.enter()

                callbacks.forEach { callback ->
                    callback.enteredState(this, stateContext.getSource(), transition, stateContext.getTarget())
                }

                globalInterceptor?.apply {
                    stateEntered(state)
                    stateChanged(currentState,state)
                    transitionEnded(transition)
                }

                currentState = state
            } else {
                println("$transition 失敗")

                globalInterceptor?.stateMachineError(this, StateMachineException("狀態轉換時 guard [${guard}], 狀態 [${currentState.name}],事件 [${e.javaClass.simpleName}]"))
            }
        } catch (exception:Exception) {

            globalInterceptor?.stateMachineError(this, StateMachineException("This state [${this.currentState.name}] doesn't support transition on ${e.javaClass.simpleName}"))
        }
    }

    @Synchronized
    fun getCurrentState(): BaseState = this.currentState.name

    /**
     * 註冊 TransitionCallback
     */
    fun registerCallback(transitionCallback: TransitionCallback) = transitionCallbacks.add(transitionCallback)

    /**
     * 取消 TransitionCallback
     */
    fun unregisterCallback(transitionCallback: TransitionCallback) = transitionCallbacks.remove(transitionCallback)

    companion object {

        fun buildStateMachine(initialStateName: BaseState, init: StateMachine.() -> Unit): StateMachine {
            val stateMachine = StateMachine(initialStateName)
            stateMachine.init()
            return stateMachine
        }
    }
}

在 StateMachine 中,包含了一個全局的 GlobalInterceptor 和 一個 TransitionCallback 的列表。

GlobalInterceptor

能夠監聽 State、Transition、StateContext 以及異常。

interface GlobalInterceptor {

    /**
     * 進入某個 State
     */
    fun stateEntered(state: State)

    /**
     * 離開某個 State
     */
    fun stateExited(state: State)

    /**
     * State 發生改變
     * @param from: 當前狀態
     * @param to:   下一個狀態
     */
    fun stateChanged(from: State, to: State)

    /**
     * 觸發 Transition
     */
    fun transition(transition: Transition)

    /**
     * 準備開始 Transition
     */
    fun transitionStarted(transition: Transition)

    /**
     * Transition 結束
     */
    fun transitionEnded(transition: Transition)

    /**
     * 狀態機異常的回調
     */
    fun stateMachineError(stateMachine: StateMachine, exception: Exception)

    /**
     * 監聽狀態機上下文
     */
    fun stateContext(stateContext: StateContext)
}

TransitionCallback

只能監聽 Transition 發生的變化,也就是進入 State、離開 State。

interface TransitionCallback {

    fun enteringState(
        stateMachine: StateMachine,
        currentState: BaseState,
        transition: Transition,
        targetState: BaseState
    )

    fun enteredState(
        stateMachine: StateMachine,
        previousState: BaseState,
        transition: Transition,
        currentState: BaseState
    )
}

TypeAliases

定義了狀態內部執行的 action、Transition 執行的 action、以及是否執行 Transition 的斷言。

typealias StateAction = (State) -> Unit

typealias TransitionAction = (Transition) -> Unit

typealias Guard = ()->Boolean

支持 RxJava 2

通過對 StateMachine 增加擴展屬性 enterTransitionObservable、exitTransitionObservable 可以監聽到進入 State、離開 State 發生的變化。

val StateMachine.stateObservable: Observable<TransitionEvent>
    get() = Observable.create { emitter ->
        val rxCallback = RxStateCallback(emitter)
        registerCallback(rxCallback)
        emitter.setCancellable {
            unregisterCallback(rxCallback)
        }
    }

val StateMachine.enterTransitionObservable: Observable<TransitionEvent.EnterTransition>
    get() = stateObservable
        .filter { event -> event is TransitionEvent.EnterTransition }
        .map { event -> event as TransitionEvent.EnterTransition }

val StateMachine.exitTransitionObservable: Observable<TransitionEvent.ExitTransition>
    get() = stateObservable
        .filter { event -> event is TransitionEvent.ExitTransition }
        .map { event -> event as TransitionEvent.ExitTransition }

四. 應用

舉一個簡單的例子,用 FSM 來模擬用戶從初始狀態,到喫飯的狀態,最後到看電視的狀態。

fun main() {

    val sm = StateMachine.buildStateMachine(Initial()) {

        state(Initial()) {
            action {
                println("Entered [$it] State")
            }

            transition(Cook(), Eat()) {
                action {
                    println("Action: Wash Vegetables")
                }

                action {
                    println("Action: Cook")
                }
            }
        }

        state(Eat()) {

            action {
                println("Entered [$it] State")
            }

            transition(WashDishes(), WatchTV()) {

                action {
                    println("Action: Wash Dishes")
                }

                action {
                    println("Action: Turn on the TV")
                }
            }
        }

        state(WatchTV()) {

            action {
                println("Entered [$it] State")
            }
        }
    }

    sm.initialize()
    sm.sendEvent(Cook())
    sm.sendEvent(WashDishes())
}

執行結果:

Entered [Initial] State
Action: Wash Vegetables
Action: Cook
Entered [Eat] State
Action: Wash Dishes
Action: Turn on the TV
Entered [WatchTV] State

五. 總結

之所以開發一款 FSM 框架,主要是爲了重構公司的項目。趁疫情期間正好把以前的項目捋一捋。目前打算將這個 FSM 應用在我們的移動端和後端的項目上。

參考資料:

  1. 狀態機思維
  2. https://codereview.stackexchange.com/questions/143726/event-driven-finite-state-machine-dsl-in-kotlin
  3. 計算機的計算(一) - 有限自動機
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章