1. 程式人生 > 其它 >我是狀態機,有一顆永遠騷動的機器引擎

我是狀態機,有一顆永遠騷動的機器引擎

之前有小夥伴問我 async/await語法糖編譯後其實是狀態機模型,到底什麼是狀態機?

狀態機是一種行為設計模式,它允許物件在其內部狀態改變時改變其行為。看起來好像物件改變了它的類。

請仔細理解上面每一個字。

我們以自動售貨機為例,為簡化演示,我們假設自動售貨機只有1種商品, 故自動售貨機有itemCountitemPrice 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介面。 golang這種繼承、實現的做法真的好秀。

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


本文來自部落格園,作者:{有態度的馬甲},轉載請註明原文連結:https://www.cnblogs.com/JulianHuang/p/15304184.html

歡迎關注我的原創高價值公眾號