【设计模式】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 "播放状态"
}
相关推荐
短剑重铸之日4 小时前
《设计模式》第六篇:装饰器模式
java·后端·设计模式·装饰器模式
茶本无香7 小时前
设计模式之十二:模板方法模式Spring应用与Java示例详解
java·设计模式·模板方法模式
wangmengxxw13 小时前
设计模式 -详解
开发语言·javascript·设计模式
进击的小头13 小时前
设计模式落地的避坑指南(C语言版)
c语言·开发语言·设计模式
短剑重铸之日13 小时前
《设计模式》第五篇:策略模式
java·后端·设计模式·策略模式
前端不太难14 小时前
HarmonyOS 为何用 Ability 约束游戏?
游戏·状态模式·harmonyos
新缸中之脑14 小时前
5个AI设计的音乐 UI 比较
人工智能·ui·状态模式
HL_风神14 小时前
C++设计模式学习-工厂方法模式
c++·学习·设计模式
前端不太难14 小时前
游戏在 HarmonyOS 上如何“活”?
游戏·状态模式·harmonyos
琹箐14 小时前
设计模式——策略模式
设计模式·策略模式