如何以面向對象的思想設計有限狀態機

2021-01-09 計算機java編程

狀態機的概念

有限狀態機又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學計算模型,用英文縮寫也被簡稱為 FSM。FSM 會響應「事件」而改變狀態,當事件發生時,就會調用一個函數,而且 FSM 會執行動作產生輸出,所執行的動作會因為當前系統的狀態和輸入的事件不同而不同。

問題背景

為了更好地描述狀態機的應用,這裡用一個地鐵站的閘機為背景,簡單敘述一下閘機的工作流程:通常閘機默認是關閉的,當閘機檢測到有效的卡片信息後,打開閘機,當乘客通過後,關閉閘機;如果有人非法通過,那麼閘機就會產生報警,如果閘機已經打開,而乘客仍然在刷卡,那麼閘機將會顯示票價和餘額,並在屏幕輸出「請通過,謝謝」。在了解了閘機的工作流程之後,我們就可以畫出閘機的狀態圖,狀態圖如下:

在上圖中,線條上面的字表示的是:閘機輸入事件/閘機執行動作,方框內表示的是閘機的狀態。

除了使用狀態圖來表示系統的工作流程外,我們也可以採用狀態表的方式來表示系統的工作流程,狀態表如下所示:

通過上述我們已經知道閘機的工作流程了,接下來我們來看具體的實現。

代碼實現

嵌套的 switch 語句

使用嵌套的 switch 語句是最為直接的辦法,也是最容易想的方法,第一層 switch 用於狀態管理,第二層 switch 用於管理各個狀態下的各個事件。代碼實現可以用下述偽代碼來實現:

上述代碼雖然很直觀,但是狀態和事件都出現在一個處理函數中,對於一個大型的 FSM 中,可能存在大量的狀態和事件,那麼代碼量將是非常冗長的。為了解決這個問題,可以採用狀態轉移表的方法來處理。

狀態轉移表

為了減少代碼的長度,可以使用查表法,將各個信息存放於一個表中,根據事件和狀態查找表項,找到需要執行的動作以及即將轉換的狀態。

從上述我們可以看到如果要往狀態機中添加新的流程,那麼只需要往狀態表中添加東西就可以了,也就是說整個狀態機的維護及管理只需要把重心放到狀態轉移表的維護中就可以了,從代碼量也可以看出來,採用狀態轉移表的方法相比於第一種方法也大大地縮減了代碼量,而且也更容易維護。但是對於狀態轉移表來說,缺點也是顯而易見的,對於大型的 FSM 來說,遍歷狀態轉移表需要花費大量的時間,從而影響代碼的執行效率。那要怎樣設計代碼量少,又不需要以遍歷狀態轉移表的形式從而花費大量時間的狀態機呢?這個時候就需要以面向對象的思想來設計有限狀態機。

面向對象法設計狀態機

面向對象基本概念

以面向對象的思想實現的狀態機,大量涉及了對於函數指針的用法,必須對這個概念比較熟悉

上述所提到了兩個設計方法都是基於面向過程的一種設計思想,面向過程編程(POP)是一種以過程為中心的編程思想,以正在發生的事件為主要目標,指導開發者利用算法作為基本構建塊構建複雜系統。即將所要介紹的面向對象編程(OOP)是利用類和對象作為基本構建塊,因此分解系統時,可以從算法開始,也可以從對象開始,然後利用所得到的結構作為框架構建系統。提到面向對象編程,那自然繞不開面向對象的三個基本特徵:

封裝:隱藏對象的屬性和實現細節,僅僅對外公開接口繼承:使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展,C 語言使用 struct 的特性實現繼承多態性:使用相同的方法,根據對象的類型調用不同的處理函數。上述對於面向對象的三個基本特徵做了一個簡單的介紹,封裝和繼承的概念都都比較清晰,多態性這個特點可能會有所迷惑,在這裡筆者用在書中看到一個例子來解釋多態性,例子是這樣的:要求畫一個形狀,這個形狀是可能是圓形,矩形,星形,無論是什麼圖形,其共性都是需要調用一個畫的方法來進行繪製,繪製的形狀可以通過函數指針調用各自的繪圖代碼繪製,這就是多態的意義,根據對象的類型調用不同的處理函數。在介紹了上述很基本的概念之後,我們來看狀態機的設計。

實現細節

上述代碼的思想實現的有限狀態機相比於前兩種不需要進行大量的遍歷,也不會導致代碼量的冗長,看似已經比較完美了,但是我們再仔細想想,如果此時狀態更改了,那 turnstile_card 函數和 turnstile_pass 函數都要更改,也就是說事件和狀態存在著耦合,這與「高內聚,低耦合」的思想所違背,也就是說如果我們要繼續優化代碼,那需要對事件和狀態進行解耦。

狀態和事件解耦

將事件與狀態相分離,從而使得各個狀態的事件處理函數非常的單一,因此在這裡需要定義一個狀態類:

我們由淺入深地來思考這個問題,首先我們可以想到把閘機當作一個對象,那麼這個這個對象的職責就是處理 card 事件(刷卡)和 pass 事件(通過閘機),閘機會根據當前的狀態執行不同的動作,也就有了如下的代碼:

在定義了狀態類之後,我們就可以使用狀態類創建 lock 和 unlock 的實例並初始化。

在這裡需要補充一下上述初始化項裡函數裡的具體實現。

這樣,也就實現了狀態與事件的解耦,閘機不再需要判斷當前的狀態,而是直接調用不同狀態提供的 card() 和 pass() 方法。定義了狀態類之後,由於閘機是整個系統的中心,我們還需要定義閘機類,由於 turnstile_state_t 中只存在方法,並不存在屬性,那麼我們可以這樣來定義閘機類:

到這裡,我們已經定義了閘機類,閘機狀態類,以及閘機狀態類實例,他們之間的關係如下圖所示:

通過圖中我們也可以看到閘機類是繼承於閘機狀態類的,locked_state 和 unlocked_state 實例是由閘機狀態類派生而來的,那最底下的那個箭頭是為什麼呢?這是在後面需要講到的對於閘機狀態轉換的處理,在獲取輸入事件調用具體的方法進行處理後,我們需要修改閘機類的p_state,所以也就有了這個箭頭。

相比於最開始定義的閘機類,這個顯得更加簡潔了,同時 p_state 可以指向相應的狀態對象,從而調用相應的事件處理函數。

在定義了一個閘機類之後,就可以通過閘機類定義一個閘機實例:

turnstile_t turnstile;

然後通過函數進行初始化:

整個系統閘機作為中心,進而需要定義閘機類的事件處理方法,定義方法如下:

到這裡,我們回顧前文所述,我們已經能夠對閘機進行初始化並使得閘機根據不同的狀態執行不同的處理函數了,再回顧整個閘機的工作流程,我們發現閘機在工作的時候會涉及到從 locked 狀態到 unlocked 狀態的相互變化,也就是狀態的轉移,因此狀態轉移函數可以這樣實現:

而狀態的轉移是在事件處理之後進行變化的。那麼我們可以這樣修改處理函數,這裡用輸出語句替代閘機動作執行函數:

既然處理函數都發生了變化,那麼閘機狀態類也應該發生更改,更改如下:

但是回顧之前我們給出的閘機類和閘機狀態類的關係,閘機類是繼承於閘機狀態類的,也就是說先有的閘機狀態類後有的閘機類,但是這裡卻在閘機狀態類的方法中使用了閘機類的參數,其實這樣也是可行的,需要提前對閘機類進行處理,總的閘機類狀態類定義如下:

上述就是所有的關於狀態機的相關定義了,下面通過上述的定義實現狀態機的實現:

上述代碼運行結果如下:

結論

以上便是筆者關於狀態機的全部總結,講述了面向過程和面向對象兩種實現方法,雖然從篇幅上看面向對象的方法要更為複雜,但是代碼的執行效率以及長度都要優於面向過程的方法,所以了解面向對象的程序設計方法是很有必要的。

相關焦點

  • 基於面向對象的思想來使用結構體,將會有意想不到的效果
    接著定義通信協議結構體,然後再說明C語言方式使用結構體的方法,再介紹基於面向對象的思想來使用結構體,從而體會兩者方式之間的區別,最後再介紹如何採用模版方式來更好地獲取結構格式不定的數據內容。1、定義實現簡單的服務端類JTcpServer, 首先構造函數創建QTcpServer對象用來啟動監聽等待新的連接,當有新的連接請求的時候,則通過QTcpServer提供的接口nextPendingConnection來返回連接成功的socket, 然後等待客戶端發送數據,如果有可讀數據,那麼讀取數據進行處理。
  • 經典:面向對象編程,我的思想(上部)
    思想!精通一門程式語言(最好是面向對象的語言)後再去搞其他的程式語言,你會發現過程是如此的行雲流水!為什麼?你已經把編程的思想掌握了,再去學其他的,無非是學習一種新的語法格式了。我在這裡並不是和你討論怎麼去用C++或JAVA,也不是和你討論怎麼去學他們,我要和你討論的是怎麼去理解面向對象。
  • 一句話概述面向對象思想,徹底理解面向對象編程
    面向對象是把一組數據結構和處理他們的方法組成對象,把具有相同行為的對象歸納成類,通過封裝隱藏類的內部細節,通過繼承使類得到泛化,通過多態實現基於對象類型的動態分派。之前在面試Java的時候遇到關於面向對象的問題,好久沒複習,概念都忘了,當時沒能回答完整。今天整理了一下,徹底搞懂了什麼叫面向對象,下面用通俗的話詳細講解面向對象的相關概念。
  • 什麼是面向對象,如何理解它?
    面向對象簡稱是OOP,是目前主流的編程思想,可能許多讀者並不了解,希望能通過下面的講解能讓大家了解什麼是面向對象。JavaScript本身也是面向對象的程式語言,對於動態網頁行為的編程,只要稍微對它有了解就可以的。
  • 科普文,面向對象程序設計,要知道的那點事
    面向對象程序設計更簡單,編程者需要關心的事情,就那點事。一、什麼是對象?在現實生活中,每一個具體事物,都是對象!在程序設計中,每一個可以操作的實體,就是對象。是具有屬性和方法的實體。舉個例子吧!表單、標籤、命令按鈕都是對象。二、什麼是對象的屬性和屬性值?在現實生活中,每個對象都有自己的特性,我們可以具現化成一個具體的值。某喵,體重2斤,梨花毛,出生日期2020年8月4日。在面向對象程序設計中,對象的特性稱為對象的屬性。對象特性的具體值,被稱為屬性值。比如,程式設計師可以給不同的命令按鈕,賦予不同的屬性值。
  • 什麼是面向過程和面向對象編程
    一張圖帶你看懂什麼是面向過程和面向對象編程兩種思想的對比:面向過程是具體的東西,而且面向過程是面向對象的基礎。比如開汽車去某個地方,你就需要先有個汽車而且你需要會開車,而汽車有開車,加減速和剎車等功能,關於汽車的操作每一個都需要一個具體的過程來實現總結來說就是,面向過程是一種基礎的方法,它考慮的是實際的實現。一般情況下,面向過程是自頂向下逐步求精,其最重要的是模塊化的思想方法。因此在模塊化編程的時候才會有「低耦合,高內聚」的思想來提高效率。
  • 面向對象編程的興衰
    面向對象編程(OOP)並沒有消亡。但與過去相比,它確實沒有那麼普及了。在 90 年代時,有很多面向對象編程相關的教科書和計算機科學課程。它就是「流行趨勢」。然而,隨著時間的流逝,人們開始意識到,嚴格的面向對象方法會帶來很多問題。這些問題往往會使代碼更複雜、更難以理解且更難以測試。
  • 「c 技術」第7章 面向對象的程序設計
    本章要點: 面向對象的基本概念 類的定義與對象的聲明 構造函數和析構函數 類的靜態成員和實例成員 方法重載及運算符重載的編程實現 類的繼承與多態性的編程實現 類的屬性的實現7.1 循序漸進學理論7.1.1 面向對象程序設計概述1.面向對象程序設計的由來
  • 聊聊面向對象編程的幾個基本原則
    進行面向對象編程,有下面幾個原則:一. 面向抽象原則二. 開閉原則三. 多用組合少用繼承原則四. 高內聚-低耦合原則下面首先先介紹抽象類和接口,然後介紹面向抽象編程。3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。5.阿里Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!
  • Objeck v5.6.1 發布,面向對象程序設計語言
    Objeck 是一種受 Java 啟發,同時受 Scheme 和 UML 影響的面向對象程序設計語言。Objeck 特性為快速,易於使用,輕巧且跨平臺。Objeck 把所有的數據類型都當成是對象,包含一個編譯器和虛擬機,具有內存管理和 JIT 編譯器。
  • Python面向對象編程的基本概念
    九道門商業數據分析學院提供介紹在學習面向對象的編程時。我決定深入了解它的歷史,結果令人著迷。術語「面向對象程序設計」(OOP)是艾倫·凱(Alan Kay)在1966年讀研究生時提出的。名為Simula的語言是第一種具有面向對象編程功能的程式語言。它是在1967年開發的,用於製作仿真程序,其中最重要的信息稱為對象。
  • 如何給女朋友解釋什麼是面向對象編程?
    女朋友拿著一本《面向對象編程》過來找我。什麼是面向對象?是要面向我寫代碼嗎?不是啦,這個面向對象的對象不是你這個對象啦。此時,我突然感受到了一股莫名的殺氣。什麼?你還有其他對象嗎?有我好看嗎?有我瘦嗎?不對。你不能有其他對象。什麼是面向對象?面向對象,英文名字叫Object Oriented,是一種軟體開發方法。
  • 面向對象編程會被拋棄嗎?這五大問題不容忽視
    儘管這個想法很巧妙,但直到 1981 年,面向對象編程才成為主流。在那之後,它就沒有停止過吸引新的和經驗豐富的軟體開發者。面向對象的程式設計師市場一如既往地忙碌。但是在最近幾年中,這種已有幾十年歷史的編程範式受到越來越多的批評。難道是在面向對象編程大行其道 40 年之後,技術已經超越了這種範式?
  • Java基礎入門篇之面向對象和類的定義
    Java基礎入門篇之面向對象和類的定義 本文主要介紹了面向對象概念,面向對象的三個特點封裝性、繼承性、多態性。類的定義和創建對象與使用。詳細的介紹了怎麼去定義一個類,通過案例來理解怎麼去定義的。對象的創建與使用介紹了使用的格式,通過創建對象後,可以通過對象的引用來訪問對象的成員。
  • 雲計算開發學習筆記:Python3 面向對象技術簡介
    來源:TechWeb.com.cnPython從設計之初就已經是一門面向對象的語言,正因為如此,在Python中創建一個類和對象是很容易的。本章節我們將詳細介紹Python的面向對象編程。如果你以前沒有接觸過面向對象的程式語言,那你可能需要先了解一些面向對象語言的一些基本特徵,在頭腦裡頭形成一個基本的面向對象的概念,這樣有助於你更容易的學習Python的面向對象編程。接下來我們先來簡單的了解下面向對象的一些基本特徵。面向對象技術簡介① 類(Class): 用來描述具有相同的屬性和方法的對象的集合。
  • 利用華邦四位機實現帶農曆24節氣萬年曆的低成本設計
    [摘要]: 本文描述了一種利用華邦四位機來設計帶有農曆24節氣的萬年曆的設計方法,同時對於其中軟體的算法和硬體的電路設計進行了計算和分析。 [關鍵字]:節氣, 列表法, 微處理器, 單片機, 算法   在當前的產品設計中,人們在追求產品高性能的同時,也在追求產品的低成本。
  • 什麼是面向對象,有什麼特點
    在程式語言中,面向對象的使用是非常重要的,在PHP語言中也是用了很多年才獲得這項技術。面向對象的出現是系統開發中一個偉大的改革,程式語言開始從應用程式回到數據上。面向對象將編程過程中焦點轉向建模的真是實體上,讓應用程式更加接近現實世界。下面小編為大家主要介紹面向對象。
  • 面向對象的產品觀(2):分解的藝術
    編輯導語:面向對象的產品觀是一個非常大的話題,在上一篇文章中,作者為我們分享了關於面向對象的產品觀中的抽象思維,在本篇文章中,作者又介紹了分解的藝術,看看從橫向和縱向兩個方面應該如何分解藝術吧。
  • 圖說Java中的OOPs(面向對象編程系統)基本概念
    面向對象編程是一種編程概念,其核心思想是允許用戶創建所需要的對象,然後提供處理這些對象的方法,使用者通過操作對象而獲得運算數據。本文將以簡潔的方式對面向對象編程中的概念進行梳理。1. Class(類)你可以將類理解為對一組相似實體的統稱。
  • 對周易文化設計思想的內在承繼——論《墨子》的義利觀設計思想
    【內容提要】《墨子》在義利之辯中敢於肯定「利」,主張謀取公「利」,體現了鮮明的人本主義立場,與周易文化中「道器」思想的設計原則是相通的。《墨子》還提出了一系列基於人本主義立場的設計原則,帶有明顯的功能主義特點,與周易文化的設計思想既有很大的一致性,又實現了重要的突破,具有重大的理論意義。