行为型:九. 状态模式

状态模式是什么

状态模式是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。

为什么要用状态模式

如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。

状态模式怎么实现

这里用自动售货机举例,自动售货机有四种状态(有商品,商品已请求,收到纸币,无商品)。而行为(选择商品,增加商品,插入纸币,出货)会根据状态的变化而变化。例如如果状态为无商品时,不能选择商品;而有商品时才可以选择商品。

state.go

package state

type state interface {
	addItem(int) error           // 添加商品
	requestItem() error          // 选择商品
	insertMoney(money int) error // 付钱
	dispenseItem() error         // 提供商品
}

has_item_state.go

package state

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("没有库存")
	}
	fmt.Printf("选择商品\n")
	i.vendingMachine.setState(i.vendingMachine.itemRequested)
	return nil
}

func (i *hasItemState) addItem(count int) error {
	fmt.Printf("%d 增加商品数量:\n", count)
	i.vendingMachine.incrementItemCount(count)
	return nil
}

func (i *hasItemState) insertMoney(money int) error {
	return fmt.Errorf("请先选择商品")
}
func (i *hasItemState) dispenseItem() error {
	return fmt.Errorf("请先选择商品")
}

has_money_state.go

package state

import "fmt"

// 收到纸币
type hasMoneyState struct {
	vendingMachine *vendingMachine
}

func (i *hasMoneyState) requestItem() error {
	return fmt.Errorf("正在出货,请等待")
}

func (i *hasMoneyState) addItem(count int) error {
	return fmt.Errorf("正在出货,请等待")
}

func (i *hasMoneyState) insertMoney(money int) error {
	return fmt.Errorf("正在出货,请等待")
}
func (i *hasMoneyState) dispenseItem() error {
	fmt.Println("正在出货")
	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
}

item_requested_state.go

package state

import (
	"errors"
	"fmt"
)

// 商品已选择状态
type itemRequestedState struct {
	vendingMachine *vendingMachine
}

func (i *itemRequestedState) requestItem() error {
	return fmt.Errorf("商品已经选择")
}

func (i *itemRequestedState) addItem(count int) error {
	return fmt.Errorf("正在售卖中,不能添加商品")
}

func (i *itemRequestedState) insertMoney(money int) error {
	if money < i.vendingMachine.itemPrice {
		fmt.Errorf("插入金额不足,请插入: %d", i.vendingMachine.itemPrice)
		return errors.New("插入金额不足")
	}
	fmt.Println("正在出货")
	i.vendingMachine.setState(i.vendingMachine.hasMoney)
	return nil
}
func (i *itemRequestedState) dispenseItem() error {
	return fmt.Errorf("请先投币")
}

no_item_state.go

package state

import "fmt"


// 无商品状态
type noItemState struct {
	vendingMachine *vendingMachine
}

func (i *noItemState) requestItem() error {
	return fmt.Errorf("库存不足")
}

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("库存不足")
}
func (i *noItemState) dispenseItem() error {
	return fmt.Errorf("库存不足")
}

vending_machine.go

package state

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("库存增加 %d 个商品\n", count)
	v.itemCount = v.itemCount + count
}

example.go

package state

import (
	"log"
)

func Example() {
	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())
	}
	
	err = vendingMachine.addItem(2)
	if err != nil {
		log.Fatalf(err.Error())
	}
	
	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())
	}
}

//运行结果:
//选择商品
//正在出货
//正在出货
//库存增加 2 个商品
//选择商品
//正在出货
//正在出货

优点

  1. 单一职责原则。 将与特定状态相关的代码放在单独的类中。
  2. 开闭原则。 无需修改已有状态类和上下文就能引入新状态。

缺点

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。

热门相关:超武穿梭   仗剑高歌   薄先生,情不由己   寂静王冠   寂静王冠