[go] 状态模式
状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
模型说明
-
上下文 (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