状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
模型说明
https://pic4.58cdn.com.cn/nowater/webim/big/n_v2a8bbd22e4b0a43aabfc7ab4e94293461.png
- 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
- 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。
- 具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。
- 状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。
- 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。
优缺点
1.优点
- 单一职责原则: 将与特定状态相关的代码放在单独的类中。
- 开闭原则: 无需修改已有状态类和上下文就能引入新状态。
- 通过消除臃肿的状态机条件语句简化上下文代码。
2.缺点
- 如果状态机只有很少的几个状态,或者很少发生改变,那么应用该模式可能会显得小题大作。
使用场景
- 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。
- 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。
- 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。
参考代码
一台自动售货机上使用状态设计模式:
- 有商品 (hasItem)
- 无商品 (noItem)
- 商品已请求 (itemRequested)
- 收到纸币 (hasMoney)
同时, 自动售货机也会有不同的操作。 再一次的, 为了简单起见, 我们假设其只会执行 4 种操作:
- 选择商品
- 添加商品
- 插入纸币
- 提供商品
// vendingMachine.go 自动售货机 package main import "fmt" type VendingMachine struct { hasItem State itemRequested State hasMoney State noItem State currentState State itemCount int itemPrice int } 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 } 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 }
// state.go 状态接口 package main type State interface { addItem(int) error requestItem() error insertMoney(money int) error dispenseItem() error }
// noItemState.go 无商品状态 package main 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") }
// hasItemState.go 有商品状态 package main 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") }
// itemRequestedState.go 请求商品 package main 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 { return 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") }
// hasMoneyState.go 收到钱 package main 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 }
// main.go 客户端 package main 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()) } }
output:
Item requestd Money entered is ok Dispensing Item Adding 2 items Item requestd Money entered is ok Dispensing Item