我是狀態(tài)機, 一顆永遠騷動的機器引擎
本文轉(zhuǎn)載自微信公眾號「精益碼農(nóng)」,作者小碼甲 。轉(zhuǎn)載本文請聯(lián)系精益碼農(nóng)公眾號。
狀態(tài)機是一種行為設(shè)計模式,它允許對象在其內(nèi)部狀態(tài)改變時改變其行為??雌饋砗孟駥ο蟾淖兞怂念悺?/p>
請仔細理解上面每一個字。
我們以自動售貨機為例,為簡化演示,我們假設(shè)自動售貨機只有1種商品, 故自動售貨機有itemCount 、itemPrice 2個屬性。
不考慮動作的前后相關(guān)性,自動售貨機對外暴露4種行為:
- 給自動售貨機加貨 addItem
 - 選擇商品 requestItem
 - 付錢 insertMoney
 - 出貨 dispenseItem
 
重點來了,當(dāng)發(fā)生某種行為,自動售貨機會進入如下4種狀態(tài)之一, 并據(jù)此狀態(tài)做出特定動作, 之后進入另外一種狀態(tài).....
- 有商品 hasItem
 - 無商品 noItem
 - 已經(jīng)選好商品 itemRequested
 - 已付錢 hasMoney
 
當(dāng)對象可能處于多種不同的狀態(tài)之一、根據(jù)傳入的動作更改當(dāng)前的狀態(tài), 繼續(xù)接受后續(xù)動作,狀態(tài)再次發(fā)生變化.....
這樣的模式類比于機器引擎,周而復(fù)始的工作和狀態(tài)轉(zhuǎn)化,這也是狀態(tài)機的定語叫“機Machine”的原因。
有了以上思路,我們嘗試溝通UML 偽代碼:
狀態(tài)機設(shè)計模式的偽代碼實現(xiàn):
- 所謂的機器Machine維護了狀態(tài)切換的上下文
 - 機器對外暴露的行為,驅(qū)動機器的狀態(tài)變更, 行為和狀態(tài)是有因果關(guān)系的
 - 機器到達特定的狀態(tài) 只具備特定的行為,其他行為是不被允許的, 這在外面看,貌似是對象改變了原類的行為
 
下面使用golang實現(xiàn)了 狀態(tài)機設(shè)計模型:這里你也可以看下golang 是如何體現(xiàn)OOP中的類繼承、接口實現(xiàn)
goodMachine:狀態(tài)變更上下文
- 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}) // 實現(xiàn)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
 - // 代表某種狀態(tài),能接受的某種動作
 - 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
 - }
 - // 給自動售貨機供貨-----> 有貨狀態(tài)
 - 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: 使用指針接受者實現(xiàn)了state接口的全部函數(shù),那么隱式表明*noItemState 指針類型實現(xiàn)了State接口
 
注意:noItemState 結(jié)構(gòu)體內(nèi)定義了 goodMachine, 就表明noItemState繼承了goodMachine類 ;
指針接受者 noItemState實現(xiàn)了state接口的所有函數(shù),那么我們就說*noItemState實現(xiàn)了state接口。
hasItemState: 有商品
- package main
 - import "fmt"
 - type hasItemState struct {
 - *goodMachine
 - }
 - func (v *hasItemState) addItem(count int) error {
 - v.incrementItemCount(count)
 - return nil
 - }
 - // 有人選擇了商品---> 沒貨狀態(tài)/已經(jīng)選定商品
 - 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")
 - }
 - // 付錢----> 已收錢狀態(tài)
 - 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 執(zhí)行
- 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())
 - }
 - }
 
初始化了商品數(shù)量為1,價格為10 的自動售貨機,連續(xù)掏10元錢買兩次, 隨時打印狀態(tài), 輸出如下:
- 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
 
狀態(tài)機為什么定語是機器?Machine?
狀態(tài)機表現(xiàn)了:
對象的狀態(tài)受外界行為所影響,不斷的切換,到達特定的狀態(tài)又只能接受特定的行為, 真實生動的體現(xiàn)了機器Machine引擎的特征。
本文示例亦是學(xué)習(xí)golang OOP編程的范例,golang 類繼承、接口實現(xiàn)實在是太秀了。
github: https://github.com/zaozaoniao/statemachine

















 
 
 








 
 
 
 