【设计模式】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 "播放状态"
}
相关推荐
再学一点就睡2 小时前
手撕前端常用 7 种设计模式:从原理到实战,附完整代码案例
前端·设计模式
long3163 小时前
代理设计模式
java·学习·程序人生·设计模式·代理模式
穷儒公羊3 小时前
第二章 设计模式故事会之策略模式:魔王城里的勇者传说
python·程序人生·设计模式·面试·跳槽·策略模式·设计规范
晴空雨4 小时前
💥 React 容器组件深度解析:从 Props 拦截到事件改写
前端·react.js·设计模式
Leo来编程4 小时前
设计模式4-建造者模式
设计模式·建造者模式
ss2731 天前
手写MyBatis第32弹-设计模式实战:Builder模式在MyBatis框架中的精妙应用
设计模式·mybatis·建造者模式
汤姆大聪明1 天前
【软件设计模式】策略模式
设计模式·策略模式
pengzhuofan1 天前
Java设计模式-模板方法模式
java·设计模式·模板方法模式
使二颗心免于哀伤1 天前
《设计模式之禅》笔记摘录 - 17.模板方法模式
笔记·设计模式·模板方法模式
AlenLi2 天前
JavaScript - 观察者模式的实现与应用场景
前端·设计模式