狀態模式,顧名思義,是一種基於有限狀態機制的設計模式。在這種設計模式中,行為是由相應的狀態來決定的。接下來我們會用一個售賣機的例子來說明下狀態模式。為了便於說明,我們把場景簡化一下,假設有一台售賣機只賣一種商品,且只有如下四種狀態:
- 有商品
- 無商品
- 商品請求中
- 已收款
一台售賣機也應該會有多種功能,我們同樣做一下簡化,假設這台售賣機只有四個功能:
- 選擇商品
- 補充商品
- 投幣
- 吐出商品
什么時候使用狀態模式
- 在一個對象有多種不同狀態的時候。對象需要根據當前的請求來改變它的狀態
- 在前面提到的例子中,售賣機將會受到行為的影響從一種狀態切換到另一種狀態。比如,當“投幣”行為結束后,售貨機將會從“商品請求中”狀態切換到“已收款”狀態。
- 在一個對象需要根據它當前的狀態對同一個請求做出不同響應的時候。這個時候使用狀態模式可以避免大量的條件聲明。
- 仍然以售賣機為例,當用戶想購買商品時,如果售賣機的狀態為“有商品”,它就會繼續處理,如果售賣機狀態為“無商品”,它就會拒絕處理。請注意,這里售賣機根據它“有商品”和“無商品”的狀態,對購買商品的請求作出了兩種不同的響應。
UML類圖
類圖如下:

代碼
看下代碼:
state.go:
type state interface {
addItem(int) error
requestItem() error
insertMoney(money int) error
dispenseItem() error
}
這里簡單解釋下:
在代碼中我們定義了一個State接口,這個接口中有四個函數分別表示了售賣機的四種行為,如下:
- 購買商品:addItem(int) error
- 請求商品:requestItem() error
- 投幣:insertMoney(money int) error
- 吐出商品:dispenseItem() error
每個具體的狀態實現都實現了以上四個函數,並對每種行為發生時該切換到哪種狀態,以及如何響應做了處理
每個具體的狀態也都嵌入了一個指向當前售賣機的指針,這樣以確保狀態的切換是發生在這台售賣機上。
vendingMachine.go:
import "fmt"
type vendingMachine struct {
hasItem state
itemRequested state
hasMoney state
noItem state
currentState state
itemCount int
itemPrice int
}
func (v *vendingMachine) requestItem() error {
return v.currentState.requestItem()
}
func (v *vendingMachine) addItem(count int) error {
return v.currentState.addItem(count)
}
func (v *vendingMachine) insertMoney(money int) error {
return v.currentState.insertMoney(money)
}
func (v *vendingMachine) dispenseItem() error {
return v.currentState.dispenseItem()
}
func (v *vendingMachine) setState(s state) {
v.currentState = s
}
func (v *vendingMachine) incrementItemCount(count int) {
fmt.Printf("Adding %d items\n", count)
v.itemCount = v.itemCount + count
}
注意這段代碼,這里面沒有任何條件表達式,所有邏輯處理均由相應的狀態實現完成。
下面是具體的狀態實現。
hasItemState.go:
import "fmt"
type hasItemState struct {
vendingMachine *vendingMachine
}
func (i *hasItemState) requestItem() error {
if i.vendingMachine.itemCount == 0 {
i.vendingMachine.setState(i.vendingMachine.noItem)
return fmt.Errorf("No item present")
}
fmt.Printf("Item requestd\n")
i.vendingMachine.setState(i.vendingMachine.itemRequested)
return nil
}
func (i *hasItemState) addItem(count int) error {
fmt.Printf("%d items added\n", count)
i.vendingMachine.incrementItemCount(count)
return nil
}
func (i *hasItemState) insertMoney(money int) error {
return fmt.Errorf("Please select item first")
}
func (i *hasItemState) dispenseItem() error {
return fmt.Errorf("Please select item first")
}
hasMoneyState.go:
import "fmt"
type hasMoneyState struct {
vendingMachine *vendingMachine
}
func (i *hasMoneyState) requestItem() error {
return fmt.Errorf("Item dispense in progress")
}
func (i *hasMoneyState) addItem(count int) error {
return fmt.Errorf("Item dispense in progress")
}
func (i *hasMoneyState) insertMoney(money int) error {
return fmt.Errorf("Item out of stock")
}
func (i *hasMoneyState) dispenseItem() error {
fmt.Println("Dispensing Item")
i.vendingMachine.itemCount = i.vendingMachine.itemCount - 1
if i.vendingMachine.itemCount == 0 {
i.vendingMachine.setState(i.vendingMachine.noItem)
} else {
i.vendingMachine.setState(i.vendingMachine.hasItem)
}
return nil
}
itemRequestedState.go:
import "fmt"
type itemRequestedState struct {
vendingMachine *vendingMachine
}
func (i *itemRequestedState) requestItem() error {
return fmt.Errorf("Item already requested")
}
func (i *itemRequestedState) addItem(count int) error {
return fmt.Errorf("Item Dispense in progress")
}
func (i *itemRequestedState) insertMoney(money int) error {
if money < i.vendingMachine.itemPrice {
fmt.Errorf("Inserted money is less. Please insert %d", i.vendingMachine.itemPrice)
}
fmt.Println("Money entered is ok")
i.vendingMachine.setState(i.vendingMachine.hasMoney)
return nil
}
func (i *itemRequestedState) dispenseItem() error {
return fmt.Errorf("Please insert money first")
}
noItemState.go:
import "fmt"
type noItemState struct {
vendingMachine *vendingMachine
}
func (i *noItemState) requestItem() error {
return fmt.Errorf("Item out of stock")
}
func (i *noItemState) addItem(count int) error {
i.vendingMachine.incrementItemCount(count)
i.vendingMachine.setState(i.vendingMachine.hasItem)
return nil
}
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")
}
下面是場景實現main.go:
import (
"fmt"
"log"
)
func main() {
vendingMachine := newVendingMachine(1, 10)
err := vendingMachine.requestItem()
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.insertMoney(10)
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.dispenseItem()
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println()
err = vendingMachine.addItem(2)
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println()
err = vendingMachine.requestItem()
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.insertMoney(10)
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.dispenseItem()
if err != nil {
log.Fatalf(err.Error())
}
}
func newVendingMachine(itemCount, itemPrice int) *vendingMachine {
v := &vendingMachine{
itemCount: itemCount,
itemPrice: itemPrice,
}
hasItemState := &hasItemState{
vendingMachine: v,
}
itemRequestedState := &itemRequestedState{
vendingMachine: v,
}
hasMoneyState := &hasMoneyState{
vendingMachine: v,
}
noItemState := &noItemState{
vendingMachine: v,
}
v.setState(hasItemState)
v.hasItem = hasItemState
v.itemRequested = itemRequestedState
v.hasMoney = hasMoneyState
v.noItem = noItemState
return v
}
執行后輸出為:
Item requestd Money entered is ok Dispensing Item Adding 2 items Item requestd Money entered is ok Dispensing Item
代碼已上傳至GitHub: zhyea / go-patterns / state-pattern
End!!
