我是狀態(tài)機, 一顆永遠(yuǎn)騷動的機器引擎
本文轉(zhuǎn)載自微信公眾號「精益碼農(nóng)」,作者小碼甲 。轉(zhuǎn)載本文請聯(lián)系精益碼農(nóng)公眾號。
狀態(tài)機是一種行為設(shè)計模式,它允許對象在其內(nèi)部狀態(tài)改變時改變其行為??雌饋砗孟駥ο蟾淖兞怂念悺?/p>
請仔細(xì)理解上面每一個字。
我們以自動售貨機為例,為簡化演示,我們假設(shè)自動售貨機只有1種商品, 故自動售貨機有itemCount 、itemPrice 2個屬性。
不考慮動作的前后相關(guān)性,自動售貨機對外暴露4種行為:
- 給自動售貨機加貨 addItem
- 選擇商品 requestItem
- 付錢 insertMoney
- 出貨 dispenseItem
重點來了,當(dāng)發(fā)生某種行為,自動售貨機會進(jìn)入如下4種狀態(tài)之一, 并據(jù)此狀態(tài)做出特定動作, 之后進(jìn)入另外一種狀態(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維護(hù)了狀態(tài)切換的上下文
- 機器對外暴露的行為,驅(qū)動機器的狀態(tài)變更, 行為和狀態(tài)是有因果關(guān)系的
- 機器到達(dá)特定的狀態(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)受外界行為所影響,不斷的切換,到達(dá)特定的狀態(tài)又只能接受特定的行為, 真實生動的體現(xiàn)了機器Machine引擎的特征。
本文示例亦是學(xué)習(xí)golang OOP編程的范例,golang 類繼承、接口實現(xiàn)實在是太秀了。
github: https://github.com/zaozaoniao/statemachine






