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