主頁 > 軟體設計 > 如何實作一個狀態機?

如何實作一個狀態機?

2022-07-21 10:19:33 軟體設計

何為狀態機?

  從字面上簡單粗暴地理解,狀態機是一個跟狀態有關的機器,但其實狀態機并不是一種物理機器,而是一種模型,一種表達事物狀態及狀態變化程序的數學模型,
  狀態機全稱是有限狀態機(finite-state machine,縮寫:FSM)或者有限狀態自動機(finite-state automaton,縮寫:FSA),是自動機理論的研究物件,狀態機擁有有限數量的狀態,每個狀態可以遷移到零個或多個其它狀態,狀態機的狀態及遷移程序可以用有向圖來表示,

狀態機用來干啥?

  上面介紹了狀態機的概念,很多同學可能會說:既然狀態機是數學領域中的理論,而我是程式員,這跟我有什么關系呢?確實,狀態機是屬于數學理論,要深入研究需要掌握離散數學等專業知識,但這并不意味著計算機領域不會用到,畢竟計算機科學中太多東西都是以數學作為基石的,
  在計算機科學中,或者干脆把范圍直接縮小到我們程式員的日常開發中,我們或多或少都會接觸到狀態機,例如Android的MediaPlayerMediaCodec,其實作框架里面就包含了大量的狀態管理,iOS的GKState也使用了狀態機來管理多種狀態,
  其實,在軟體開發里面,我們更多地是結合自動機理論和軟體設計思想來設計編程模式,以此構建出更加優秀的軟體,GoF 23種軟體設計模式中的狀態模式就是一種基于狀態的設計模式,

狀態機的元素

  狀態機中包含哪些元素呢?一般來講,一個狀態機包含如下元素:

  • 狀態

  即狀態機中包含的有限個數的狀態,

  • 行為

  即狀態對應的一系列行為表現,

  • 事件

  即觸發狀態發生改變的事件,

  • 轉換

  即狀態改變的程序,

  例如:我們每天經歷的白天夜晚可以看做是一個狀態機,早晨太陽從東邊升起,我們迎來了美好的白天,白天我們會吃飯、上班、運動,等到傍晚太陽從西邊落下的時候,我們便進入了靜謐的夜晚,晚上我們會看電視、學習、睡覺,如果用一個有向圖來表示這個程序,大概會是這樣:

狀態機_白天夜晚

  在白天夜晚狀態機里面,白天和黑夜屬于狀態,白天吃飯上班運動、夜晚看電視學習睡覺是狀態對應的行為表現,日出和日落是觸發狀態轉換的事件,夜晚經過日出轉換為白天、白天經過日落轉換為夜晚表示狀態轉換的程序,

狀態模式

  狀態設計模式是GoF提出的23種設計模式之一,可以看做是一種基于狀態機的設計模式,在狀態設計模式里面,包含了與狀態機對應的各項元素,即:狀態、行為、事件、轉換,設計模式和具體的編程語言無關,因此狀態設計模式也可以用多種語言來實作,

狀態模式的適用場景

  我們在什么情況下需要使用狀態模式呢?一般來講,我們在編碼的時候,如果發現物件在不同場景或不同階段會表現出不同的行為,而且行為控制邏輯比較復雜、容易混亂的時候,我們就可以考慮使用狀態模式,在狀態模式里面,我們可以根據業務邏輯為物件劃分出有限個數的狀態,每個狀態內部都封裝好對應的行為,要改變物件的行為,只需要簡單地改變物件的狀態即可,我們可以“面向狀態編程”了!這樣原本復雜的糅雜在一起的邏輯,就一下變得清晰明了了,

通過狀態模式實作狀態機

  接下來,我們將通過一個完整的示例,來演示如何通過狀態設計模式來實作一個狀態機,在示例里面,我們會實作上面的白天夜晚狀態機,鑒于面向物件的思想能夠清晰地表達狀態機中的各種元素,因此我們選用當下比較流行的Kotlin作為編碼語言,

定義狀態及行為

  首先,我們來定義狀態,白天夜晚狀態機包含白天和夜晚兩個狀態,兩個狀態都會表現出對應的行為,但是各自的行為是不一樣的,因此,可以通過介面+實作類的方式來定義狀態,這里我們抽象出了一個狀態介面IState,并在IState中宣告了表達狀態行為的run()方法,然后實作了IState的3個子類IdleStateDayStateNightState,分別表示空閑狀態、白天狀態和夜晚狀態,其中,IdleState僅作為狀態機的起始狀態,在示例里面沒有體現太多實際意義,DayStateNightState在實作run()方法時,通過輸出一段日志來表示狀態執行的具體行為,
  IState介面:

/**
 * 狀態介面
 */
interface IState {

    /**
     * 狀態要執行的行為
     */
    fun run()
}

  DayState白天狀態類:

/**
 * 白天狀態
 */
class DayState : IState {

    init {
        run()
    }

    override fun run() {
        println("進入白天,吃飯、上班、運動!")
    }
}

  NightState夜晚狀態類:

/**
 * 夜晚狀態
 */
class NightState : IState {

    init {
        run()
    }

    override fun run() {
        println("進入夜晚,看電視、學習、睡覺!")
    }
}
定義事件

  然后我們來定義狀態機的事件,在白天夜晚狀態機中,白天狀態經過日落轉為夜晚狀態,夜晚狀態經過日出轉為白天狀態,因此,狀態機中包含兩個事件,即日出和日落,
事件:

/**
 * 事件-日出
 */
const val STATE_EVENT_SUNRISE = "sunrise"

/**
 * 事件-日落
 */
const val STATE_EVENT_SUNSET = "sunset"

 

狀態轉換

  然后,我們來實作狀態的轉換,為了集中處理狀態的轉換,我們決定封裝一個專門的類StateManager來進行管理,首先,我們抽象出StateManager的父介面IStateManager,用以宣告StateManager中需要實作的各個屬性及方法,
  狀態管理介面IStateManager

/**
 * 狀態管理介面
 */
interface IStateManager {

    /**
     * 當前狀態
     */
    val state: IState

    /**
     * 根據事件轉換狀態
     *
     * @param event 事件
     */
    fun transitionState(event: String)
}

  IStateManager中宣告了表示當前狀態的變數state,同時宣告了transitionState(event: String)方法用來狀態轉換,
  狀態管理類StateManager

/**
 * 狀態管理類
 */
class StateManager : IStateManager {

    override var state: IState = IdleState()

    override fun transitionState(event: String) {
        state = when (event) {
            STATE_EVENT_SUNRISE -> DayState()
            STATE_EVENT_SUNSET -> NightState()
            else -> IdleState()
        }
    }
}

  至此,白天夜晚狀態機需要的狀態、行為、事件、轉換四個元素就已經備齊了,接下來我們可以運行狀態機了,

運行狀態機

  我們通過模擬白天夜晚變化的情境,來運行狀態機,我們通過定時任務模擬了一天當中從0點到次日0點之間24小時的變化,定時任務中1秒表示現實中的1個小時,6點日出時將狀態機的當前狀態轉換為白天狀態,18點日落時將狀態機的當前狀態轉換為夜晚狀態,
  模擬情境StatePatternSceneSimulator

/**
 * 狀態模式場景模擬器
 *
 * 通過定時任務模擬一天24小時變化,1秒表示1小時,6點日出,轉換為白天狀態,18點日落,轉換為夜晚狀態
 */
class StatePatternSceneSimulator : ISceneSimulator {

    /**
     * 狀態管理介面實體
     */
    private val stateManager: IStateManager by lazy { StateManager() }

    /**
     * 當前時間,即幾點
     */
    private var time: Int = 0

    override fun run() {
        val countDownLatch = CountDownLatch(240)
        val timer = Timer()
        timer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                println("現在是 $time 點")
                if (time == 6) {
                    // 6點日出,轉換為白天狀態
                    stateManager.transitionState(STATE_EVENT_SUNRISE)
                } else if (time == 18) {
                    // 18點日落,轉換為夜晚狀態
                    stateManager.transitionState(STATE_EVENT_SUNSET)
                }
                if (time < 23) {
                    time++
                } else {
                    time = 0
                }
                countDownLatch.countDown()
            }
        }, 0, 1000)
        countDownLatch.await()
    }

    companion object {

        /**
         * 運行場景
         */
        fun run() {
            StatePatternSceneSimulator().run()
        }
    }
}

  接下來,我們在測驗代碼中,呼叫StatePatternSceneSimulator來運行模擬情境,

/**
 * 狀態模式測驗類
 */
class Main {

    /**
     * 演示狀態模式
     */
    @Test
    fun main() {
        StatePatternSceneSimulator.run()
    }
}

  執行main()函式之后,控制臺將會輸出如下日志:

現在是 0 點
現在是 1 點
現在是 2 點
現在是 3 點
現在是 4 點
現在是 5 點
現在是 6 點
進入白天,吃飯、上班、運動!
現在是 7 點
現在是 8 點
現在是 9 點
現在是 10 點
現在是 11 點
現在是 12 點
現在是 13 點
現在是 14 點
現在是 15 點
現在是 16 點
現在是 17 點
現在是 18 點
進入夜晚,看電視、學習、睡覺!
現在是 19 點
現在是 20 點
現在是 21 點
現在是 22 點
現在是 23 點
現在是 0 點

  通過日志,我們可以看到隨著時間的變化,狀態機的狀態在白天和夜晚兩個狀態中來回轉換,至此,我們便通過狀態設計模式實作了白天夜晚狀態機!

狀態機的實際應用

  示例中的白天夜晚狀態機,只是一個最簡單的狀態機,在實際開發中,我們遇到的業務場景會比這個復雜得多,如果要通過狀態機來實作這些復雜業務,狀態機的設計本身也會變得更加復雜,我們可以通過多種形式對簡單的狀態機進行拓展,來解決更加復雜的問題場景,

分層狀態機

  所謂分層狀態機,是指狀態可以像類的繼承那樣,自上而下包含多個層級,例如在白天夜晚狀態機里面,白天狀態包含吃飯、上班、運動等行為,起初這些行為可通過簡單的代碼進行描述,吃飯就是“吃飯”,上班就是“上班”,運動就是“運動”,但是隨著業務的深入,邏輯會變得越來越復雜,吃飯不再是簡單地描述為“吃飯”,而是需要描述清楚“吃的什么菜,吃了多少,和誰一起吃的”,上班不再是簡單地描述為“上班”,而是要描述清楚“上班干了些什么,有沒有會議,是正常上班還是加班”,運動也不再是簡單地描述為“運動”,而是要描述清楚“做的那種型別的運動,運動時長是多少,消耗了多少熱量”,試想一下,如果把這些邏輯繼續放在白天狀態里面,那么白天狀態的邏輯會變得越來越復雜、越來越臃腫,甚至混亂出錯,此時,我們可以考慮將白天狀態進一步拆分,我們可以根據不同的行為,將白天狀態拆分為吃飯狀態、上班狀態、運動狀態等子狀態,每一種子狀態各自管理自己的業務,這樣拆分之后,白天狀態臃腫的邏輯被劃分到了每個子狀態中,一下子就變得清爽干凈了!

并發狀態機

  所謂并發狀態機,是指不止存在一種狀態機,而是多種狀態機并存,例如代碼里面既有維護日夜交替的白天夜晚狀態機,又有維護四季變遷的春夏秋冬狀態機,兩種狀態機包含不同的狀態以及狀態轉換邏輯,相互獨立、互不干涉,但也不排除在某些情況下,狀態機之間會進行互動,例如夏天的夜晚看星星、冬天的白天堆雪人等等,

下推自動機

  所謂下推自動機,是指通過在狀態機內部維護一個存盤狀態的堆疊來記錄狀態入堆疊和出堆疊的順序,狀態完成轉換后,新的狀態被壓入堆疊中,位于堆疊頂,前一個狀態并沒有被新的狀態直接覆寫,而是在堆疊中位于新狀態的下面,在某些場景下,如果我們需要將當前狀態恢復為之前的狀態,那么我們就可以將堆疊頂的狀態彈出,此時前一個狀態又回到了堆疊頂的位置,我們拿到堆疊頂的狀態也就是前一個狀態后,將當前狀態設定為前一個狀態,便完成了狀態的恢復,

  以上便是幾種常見的狀態機拓展應用,當然,對狀態機的拓展遠不止于此,我們可以根據具體業務需求,結合面向物件封裝、繼承、多型的思想以及各種資料結構等,實作相應的拓展,

原始碼

  [GitHub專案原始碼]

參考資料

  1. https://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA
  2. https://zh.wikipedia.org/wiki/%E8%87%AA%E5%8A%A8%E6%9C%BA%E7%BC%96%E7%A8%8B
  3. https://zh.wikipedia.org/wiki/%E8%87%AA%E5%8B%95%E6%A9%9F
  4. https://baike.baidu.com/item/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E8%87%AA%E5%8A%A8%E6%9C%BA/2850046?fromtitle=%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA&fromid=2081914&fr=aladdin
  5. https://zhuanlan.zhihu.com/p/74984237

尊重原創,轉載請注明出處:https://yuriyshea.com/archives/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%8A%B6%E6%80%81%E6%9C%BA

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/499914.html

標籤:其他

上一篇:分布式事務(Seata)

下一篇:程式設計之設計模式介紹

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more