【设计模式】16、state 状态模式

文章目录

  • [十六、state 状态模式](#十六、state 状态模式)
    • [16.1 自动购物机](#16.1 自动购物机)
      • [16.1.1 vending_machine_test.go](#16.1.1 vending_machine_test.go)
      • [16.1.2 vending_maching.go](#16.1.2 vending_maching.go)
      • [16.1.3 state.go](#16.1.3 state.go)
      • [16.1.4 no_good_state.go](#16.1.4 no_good_state.go)
      • [16.1.5 has_good_state.go](#16.1.5 has_good_state.go)
    • [16.2 player](#16.2 player)
      • [16.2.1 player_test.go](#16.2.1 player_test.go)
      • [16.2.2 player.go](#16.2.2 player.go)
      • [16.2.3 state.go](#16.2.3 state.go)
      • [16.2.4 stopped_state.go](#16.2.4 stopped_state.go)
      • [16.2.5 playing_state.go](#16.2.5 playing_state.go)

十六、state 状态模式

https://refactoringguru.cn/design-patterns/state

在不同的情况下, 执行对应的操作. 通常是由 if else 实现的. 但随着需求扩张, 代码无法维护

可以描述出各种状态(即各种 if 的条件), 把状态切换的控制流, 和状态的具体操作的业务流, 拆分开.

通常 Context 类持有 state 接口, state 接口有很多实现

每种 state 的实现, 都持有 Context 类的反向引用用于切换状态, 并只负责当前状态需执行的操作

16.1 自动购物机

https://refactoringguru.cn/design-patterns/state/go/example

自动购物机, 包括如下状态

  1. 初始为无货状态, 当供应商添加货物后, 变为有货状态
  2. 当用户选择货物后, 变为已选择货物的状态
  3. 当用户付款后, 售卖货物, 并货物数量减少, 最终回到有货状态
bash 复制代码
├── has_good_state.go
├── no_good_state.go
├── readme.md
├── state.go
├── vending_machine_test.go
└── vending_maching.go

16.1.1 vending_machine_test.go

go 复制代码
package _61vending_maching

import (
    "fmt"
    "github.com/stretchr/testify/require"
    "testing"
)

/*
=== RUN   TestVendingMachine
---case1---
[noGoodState] SelectGood start, 但因无货而无法选择货物
---case2---
[noGoodState] AddGood start
[添加商品] 开始
[添加商品] 成功
[noGoodState] AddGood success
[切换为有货状态] 开始
[切换为有货状态] 结束
[hasGoodState] SelectGood start
[选择商品] 开始
[选择商品] 成功
[hasGoodState] SelectGood success
[hasGoodState] Buy start
[购买] 开始
[pay] 开始
[pay] 成功
[移除商品] 开始
[移除商品] 成功
[购买] 成功
[hasGoodState] Buy success
[切换为无货状态] 开始
[切换为无货状态] 结束
--- PASS: TestVendingMachine (0.00s)
PASS
*/
func TestVendingMachine(t *testing.T) {
    m := NewVendingMachine()

    const good1 = "煎饼果子"

    fmt.Println("---case1---")
    m.SelectGood(good1)
    require.Len(t, m.goods, 0)

    fmt.Println("---case2---")
    m.AddGood(good1)
    require.Len(t, m.goods, 1)
    require.Empty(t, m.selectedGood)
    m.SelectGood(good1)
    require.NotEmpty(t, m.selectedGood)
    m.Buy()
    require.Len(t, m.goods, 0)
}

16.1.2 vending_maching.go

go 复制代码
package _61vending_maching

import "fmt"

// 购物机
type vendingMachine struct {
    state        state
    goods        map[string]struct{} // 所有商品列表
    selectedGood string              // 当前选中的商品
}

func NewVendingMachine() *vendingMachine {
    m := &vendingMachine{
        state:        nil, // 后续填充
        goods:        make(map[string]struct{}),
        selectedGood: "",
    }
    m.state = NewNoGoodState(m) // state 反向引用 context
    return m
}

// 切换状态
func (m *vendingMachine) changeState(s state) {
    m.state = s
}

func (m *vendingMachine) AddGood(g string) {
    m.state.AddGood(g)
}

func (m *vendingMachine) SelectGood(g string) {
    m.state.SelectGood(g)
}

func (m *vendingMachine) Buy() {
    m.state.Buy()
}

// ------- 私有方法 ------
func (m *vendingMachine) addGood(g string) {
    fmt.Println("[添加商品] 开始")

    _, exist := m.goods[g]
    if exist {
        fmt.Println("[添加商品] 已存在, 无需添加")
        return
    }
    m.goods[g] = struct{}{}
    fmt.Println("[添加商品] 成功")
}

func (m *vendingMachine) selectGood(g string) error {
    fmt.Println("[选择商品] 开始")

    _, exist := m.goods[g]
    if !exist {
        return fmt.Errorf("[选择商品] 并不存在, 无法选择")
    }

    m.selectedGood = g
    fmt.Println("[选择商品] 成功")

    return nil
}

func (m *vendingMachine) buy() {
    fmt.Println("[购买] 开始")
    m.pay()
    m.removeGood(m.selectedGood)
    m.selectedGood = ""
    fmt.Println("[购买] 成功")
}

func (m *vendingMachine) pay() {
    fmt.Println("[pay] 开始")
    fmt.Println("[pay] 成功")
}

func (m *vendingMachine) removeGood(g string) {
    fmt.Println("[移除商品] 开始")

    _, exist := m.goods[g]
    if !exist {
        fmt.Println("[移除商品] 并不存在, 无需移除")
        return
    }

    delete(m.goods, g)
    fmt.Println("[移除商品] 成功")
}

16.1.3 state.go

go 复制代码
package _61vending_maching

// 每种 state 都有如下操作
type state interface {
    // AddGood 添加商品
    AddGood(g string)
    // SelectGood 选择商品
    SelectGood(g string)
    // Buy 付款Pay+取货RemoveGood
    Buy()
}

16.1.4 no_good_state.go

go 复制代码
package _61vending_maching

import "fmt"

type noGoodState struct {
    m *vendingMachine
}

func NewNoGoodState(m *vendingMachine) state {
    return &noGoodState{m: m}
}

func (s *noGoodState) AddGood(g string) {
    fmt.Println("[noGoodState] AddGood start")
    s.m.addGood(g)
    fmt.Println("[noGoodState] AddGood success")

    fmt.Println("[切换为有货状态] 开始")
    s.m.changeState(NewHasGoodState(s.m))
    fmt.Println("[切换为有货状态] 结束")
}

func (s *noGoodState) SelectGood(g string) {
    fmt.Println("[noGoodState] SelectGood start, 但因无货而无法选择货物")
}

func (s *noGoodState) Buy() {
    fmt.Println("[noGoodState] Buy start, 但因无货而无法购买")
}

16.1.5 has_good_state.go

go 复制代码
package _61vending_maching

import "fmt"

type hasGoodState struct {
    m *vendingMachine
}

func NewHasGoodState(m *vendingMachine) state {
    return &hasGoodState{m: m}
}

func (s *hasGoodState) AddGood(g string) {
    fmt.Println("[hasGoodState] AddGood start")
    s.m.addGood(g)
    fmt.Println("[hasGoodState] AddGood success")
}

func (s *hasGoodState) SelectGood(g string) {
    fmt.Println("[hasGoodState] SelectGood start")
    s.m.selectGood(g)
    fmt.Println("[hasGoodState] SelectGood success")
}

func (s *hasGoodState) Buy() {
    fmt.Println("[hasGoodState] Buy start")
    s.m.buy()
    fmt.Println("[hasGoodState] Buy success")

    if len(s.m.goods) == 0 {
        fmt.Println("[切换为无货状态] 开始")
        s.m.changeState(NewHasGoodState(s.m))
        fmt.Println("[切换为无货状态] 结束")
    }
}

16.2 player

音乐播放器, 有 Stopped, Playing 两种状态

https://refactoringguru.cn/design-patterns/state/rust/example

bash 复制代码
├── player.go
├── player_test.go
├── playing_state.go
├── readme.md
├── state.go
└── stopped_state.go

16.2.1 player_test.go

go 复制代码
package _62player

import "testing"

/*
=== RUN   TestPlayer
在 暂停状态, 点击暂停按钮后, 无效果
在 暂停状态, 点击播放按钮后, 切换为播放状态
[切换状态] 暂停状态 => 播放状态
在 播放状态, 点击暂停按钮后, 切换为暂停状态
[切换状态] 播放状态 => 暂停状态
--- PASS: TestPlayer (0.00s)
PASS
*/
func TestPlayer(t *testing.T) {
	p := NewPlayer()
	p.Stop()

	p.Play()
	p.Stop()
}

16.2.2 player.go

go 复制代码
package _62player

import "fmt"

type player struct {
	state state
}

func NewPlayer() *player {
	p := &player{}
	s := NewStoppedState(p) // state 持有 Context 的反向引用
	p.state = s             // Context 持有 state
	return p
}

func (p *player) ChangeState(s state) {
	fmt.Printf("[切换状态] %v => %v\n", p.state.GetStateName(), s.GetStateName())
	p.state = s
}

func (p *player) Play() {
	p.state.Play()
}

func (p *player) Stop() {
	p.state.Stop()
}

16.2.3 state.go

go 复制代码
package _62player

type state interface {
	Play()
	Stop()
	GetStateName() string
}

16.2.4 stopped_state.go

go 复制代码
package _62player

import "fmt"

type StoppedState struct {
	player *player
}

func NewStoppedState(player *player) state {
	return &StoppedState{player: player}
}

func (s *StoppedState) Play() {
	fmt.Printf("在 %v, 点击播放按钮后, 切换为播放状态\n", s.GetStateName())
	s.player.ChangeState(NewPlayingState(s.player))
}

func (s *StoppedState) Stop() {
	fmt.Printf("在 %v, 点击暂停按钮后, 无效果\n", s.GetStateName())
}

func (s *StoppedState) GetStateName() string {
	return "暂停状态"
}

16.2.5 playing_state.go

go 复制代码
package _62player

import "fmt"

type PlayingState struct {
	player *player
}

func NewPlayingState(player *player) state {
	return &PlayingState{player: player}
}

func (s *PlayingState) Play() {
	fmt.Printf("在 %v, 点击播放按钮后, 无效果\n", s.GetStateName())
}

func (s *PlayingState) Stop() {
	fmt.Printf("在 %v, 点击暂停按钮后, 切换为暂停状态\n", s.GetStateName())
	s.player.ChangeState(NewStoppedState(s.player))
}

func (s *PlayingState) GetStateName() string {
	return "播放状态"
}
相关推荐
努力学习的明29 分钟前
Spring Bean 生命周期中设计模式的应用与解析
java·spring·设计模式·生命周期
77tian3 小时前
设计模式的原理及深入解析
java·开发语言·单例模式·设计模式·代理模式·享元模式·原型模式
wu~97021 小时前
手撕四种常用设计模式(工厂,策略,代理,单例)
java·单例模式·设计模式·代理模式·抽象工厂模式·策略模式
Yvonne爱编码1 天前
CSS- 4.4 固定定位(fixed)& 咖啡售卖官网实例
前端·css·html·状态模式·hbuilder
敲代码的 蜡笔小新1 天前
【行为型之访问者模式】游戏开发实战——Unity灵活数据操作与跨系统交互的架构秘诀
unity·设计模式·c#·访问者模式
软考真题app2 天前
软件设计师考试结构型设计模式考点全解析
设计模式·软件设计师·结构型设计模式·考试考点
Yvonne爱编码2 天前
CSS- 4.2 相对定位(position: relative)
前端·css·状态模式·html5·hbuilder
鸡吃丸子2 天前
常见的实时通信技术(轮询、sse、websocket、webhooks)
前端·websocket·状态模式
xiaolin03332 天前
【设计模式】- 行为型模式1
设计模式·状态模式·责任链模式·策略模式·命令模式·模板方法模式·行为型模式
沐土Arvin2 天前
深入理解 requestIdleCallback:浏览器空闲时段的性能优化利器
开发语言·前端·javascript·设计模式·html