【设计模式】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 "播放状态"
}
相关推荐
问道飞鱼9 分钟前
【前端知识】从前端请求到后端返回:Gzip压缩全链路配置指南
前端·状态模式·gzip·请求头
繁华似锦respect5 小时前
HTTPS 中 TLS 协议详细过程 + 数字证书/签名深度解析
开发语言·c++·网络协议·http·单例模式·设计模式·https
数智研发说5 小时前
智汇电器携手鼎捷PLM:从“制造”迈向“智造”,构建高效协同研发新范式
大数据·人工智能·设计模式·重构·制造·设计规范
seven_7678230986 小时前
DevUI表单引擎实战:可配置化动态表单与多级联动设计
状态模式·devui·matechat
繁华似锦respect7 小时前
Linux - KCP 协议深度解析:原理、与 TCP/UDP 的对比及应用场景
linux·tcp/ip·观察者模式·设计模式·udp
太阳以西阿7 小时前
【设计模式03】命令设计模式(行为型设计模式)
设计模式
太阳以西阿7 小时前
【设计模式02】策略设计模式(行为型设计模式)
设计模式
雨中飘荡的记忆7 小时前
设计模式之享元模式详解
java·设计模式·享元模式
Blossom.1187 小时前
基于多智能体协作的AIGC内容风控系统:从单点检测到可解释裁决链
人工智能·python·深度学习·机器学习·设计模式·aigc·transformer
Jomurphys8 小时前
设计模式 - 责任链模式 Chain of Responsibility Pattern
android·设计模式·责任链模式