我是狀態機, 一顆永遠騷動的機器引擎
本文轉載自微信公眾號「精益碼農」,作者小碼甲 。轉載本文請聯系精益碼農公眾號。
狀態機是一種行為設計模式,它允許對象在其內部狀態改變時改變其行為。看起來好像對象改變了它的類。
請仔細理解上面每一個字。
我們以自動售貨機為例,為簡化演示,我們假設自動售貨機只有1種商品, 故自動售貨機有itemCount 、itemPrice 2個屬性。
不考慮動作的前后相關性,自動售貨機對外暴露4種行為:
- 給自動售貨機加貨 addItem
- 選擇商品 requestItem
- 付錢 insertMoney
- 出貨 dispenseItem
重點來了,當發生某種行為,自動售貨機會進入如下4種狀態之一, 并據此狀態做出特定動作, 之后進入另外一種狀態.....
- 有商品 hasItem
- 無商品 noItem
- 已經選好商品 itemRequested
- 已付錢 hasMoney
當對象可能處于多種不同的狀態之一、根據傳入的動作更改當前的狀態, 繼續接受后續動作,狀態再次發生變化.....
這樣的模式類比于機器引擎,周而復始的工作和狀態轉化,這也是狀態機的定語叫“機Machine”的原因。
有了以上思路,我們嘗試溝通UML 偽代碼:
狀態機設計模式的偽代碼實現:
- 所謂的機器Machine維護了狀態切換的上下文
- 機器對外暴露的行為,驅動機器的狀態變更, 行為和狀態是有因果關系的
- 機器到達特定的狀態 只具備特定的行為,其他行為是不被允許的, 這在外面看,貌似是對象改變了原類的行為
下面使用golang實現了 狀態機設計模型:這里你也可以看下golang 是如何體現OOP中的類繼承、接口實現
goodMachine:狀態變更上下文
- package main
- import (
- "fmt"
- "reflect"
- )
- type goodMachine struct {
- currentState state
- itemCount int
- itemPrice int
- }
- func newGoodMachine(itemCount, itemPrice int) *goodMachine {
- v := &goodMachine{
- itemCount: itemCount,
- itemPrice: itemPrice,
- }
- if itemCount <= 0 {
- v.setState(&noItemState{v}) // 實現state接口的是*noItemState 指針類型
- } else {
- v.setState(&hasItemState{v})
- }
- return v
- }
- func (v *goodMachine) setState(s state) {
- fmt.Println("enter state: ", reflect.TypeOf(s))
- v.currentState = s
- }
- func (v *goodMachine) requestItem() error {
- return v.currentState.requestItem()
- }
- func (v *goodMachine) addItem(count int) error {
- return v.currentState.addItem(count)
- }
- func (v *goodMachine) insertMoney(money int) error {
- return v.currentState.insertMoney(money)
- }
- func (v *goodMachine) incrementItemCount(count int) {
- v.itemCount += count
- }
- func (v goodMachine) dispenseItem() error {
- return v.currentState.dispenseItem()
- }
自動售貨機對外的行為,被委托給特定的state對象
state:自動售貨機對外暴露的行為
- package main
- // 代表某種狀態,能接受的某種動作
- type state interface {
- addItem(count int) error
- requestItem() error
- insertMoney(money int) error
- dispenseItem() error
- }
noItemState : 無商品
- package main
- import "fmt"
- type noItemState struct {
- *goodMachine // 存在匿名類型 goodMachine,類型是*goodMachine
- }
- // 給自動售貨機供貨-----> 有貨狀態
- func (i *noItemState) addItem(count int) error {
- i.incrementItemCount(count)
- i.setState(&hasItemState{i.goodMachine})
- return nil
- }
- func (i *noItemState) requestItem() error {
- return fmt.Errorf("item out of stock")
- }
- func (i *noItemState) insertMoney(money int) error {
- return fmt.Errorf("item out of stock")
- }
- func (i *noItemState) dispenseItem() error {
- return fmt.Errorf("item out of stock")
- }
- // golang: 使用指針接受者實現了state接口的全部函數,那么隱式表明*noItemState 指針類型實現了State接口
注意:noItemState 結構體內定義了 goodMachine, 就表明noItemState繼承了goodMachine類 ;
指針接受者 noItemState實現了state接口的所有函數,那么我們就說*noItemState實現了state接口。
hasItemState: 有商品
- package main
- import "fmt"
- type hasItemState struct {
- *goodMachine
- }
- func (v *hasItemState) addItem(count int) error {
- v.incrementItemCount(count)
- return nil
- }
- // 有人選擇了商品---> 沒貨狀態/已經選定商品
- func (v *hasItemState) requestItem() error {
- if v.goodMachine.itemCount == 0 {
- v.setState(&noItemState{v.goodMachine})
- return fmt.Errorf("no item present")
- }
- fmt.Print("item requested\n")
- v.setState(&itemRequestedState{v.goodMachine})
- return nil
- }
- func (v *hasItemState) insertMoney(money int) error {
- return fmt.Errorf("Please select item first")
- }
- func (v *hasItemState) dispenseItem() error {
- return fmt.Errorf("Please select item first")
- }
itemRequestedState:有人選定商品
- package main
- import "fmt"
- type itemRequestedState struct {
- *goodMachine
- }
- func (i *itemRequestedState) addItem(count int) error {
- return fmt.Errorf("shopping is in process")
- }
- func (i *itemRequestedState) requestItem() error {
- return fmt.Errorf("item already requested")
- }
- // 付錢----> 已收錢狀態
- func (i *itemRequestedState) insertMoney(money int) error {
- if money < i.goodMachine.itemPrice {
- fmt.Errorf("insert money is less, please insert %d", i.goodMachine)
- }
- fmt.Println("money entered is ok")
- i.setState(&hasMoneyState{i.goodMachine})
- return nil
- }
- func (i *itemRequestedState) dispenseItem() error {
- return fmt.Errorf("please insert money first")
- }
hasMoneyState:已付錢
- package main
- import "fmt"
- type hasMoneyState struct {
- *goodMachine
- }
- func (i *hasMoneyState) addItem(count int) error {
- return fmt.Errorf("shopping is in process")
- }
- func (i *hasMoneyState) requestItem() error {
- return fmt.Errorf("shopping is in process")
- }
- func (i *hasMoneyState) insertMoney(money int) error {
- return fmt.Errorf("already pay money")
- }
- func (i *hasMoneyState) dispenseItem() error {
- fmt.Println("dispensing item")
- i.goodMachine.itemCount = i.goodMachine.itemCount - 1
- if i.goodMachine.itemCount == 0 {
- i.setState(&noItemState{i.goodMachine})
- } else {
- i.setState(&hasItemState{i.goodMachine})
- }
- return nil
- }
main.go 執行
- package main
- import (
- "fmt"
- "log"
- )
- func main() {
- goodMachine := newGoodMachine(1, 10)
- err := goodMachine.requestItem()
- if err != nil {
- log.Fatalf(err.Error())
- }
- err = goodMachine.insertMoney(10)
- if err != nil {
- log.Fatalf(err.Error())
- }
- err = goodMachine.dispenseItem()
- if err != nil {
- log.Fatalf(err.Error())
- }
- fmt.Println()
- err = goodMachine.requestItem()
- if err != nil {
- log.Fatalf(err.Error())
- }
- err = goodMachine.insertMoney(10)
- if err != nil {
- log.Fatal(err.Error())
- }
- err = goodMachine.dispenseItem()
- if err != nil {
- log.Fatalf(err.Error())
- }
- }
初始化了商品數量為1,價格為10 的自動售貨機,連續掏10元錢買兩次, 隨時打印狀態, 輸出如下:
- enter state: *main.hasItemState
- item requested
- enter state: *main.itemRequestedState
- money entered is ok
- enter state: *main.hasMoneyState
- dispensing item
- enter state: *main.noItemState
- 2021/08/11 17:39:45 item out of stock
- exit status 1
狀態機為什么定語是機器?Machine?
狀態機表現了:
對象的狀態受外界行為所影響,不斷的切換,到達特定的狀態又只能接受特定的行為, 真實生動的體現了機器Machine引擎的特征。
本文示例亦是學習golang OOP編程的范例,golang 類繼承、接口實現實在是太秀了。
github: https://github.com/zaozaoniao/statemachine