我是状态机, 一颗永远骚动的机器引擎

简介: 状态机是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。看起来好像对象改变了它的类。

我们以自动售货机为例,为简化演示,我们假设自动售货机只有1种商品, 故自动售货机有itemCountitemPrice 2个属性


不考虑动作的前后相关性,自动售货机对外暴露4种行为:


给自动售货机加货 addItem

选择商品 requestItem

付钱 insertMoney

出货 dispenseItem


重点来了,当发生某种行为,自动售货机会进入如下4种状态之一, 并据此状态做出特定动作, 之后进入另外一种状态.....


有商品 hasItem

无商品 noItem

已经选好商品 itemRequested

已付钱 hasMoney


a06e7b74e7db4d379012ad705ba81cee.png


当对象可能处于多种不同的状态之一、根据传入的动作更改当前的状态, 继续接受后续动作,状态再次发生变化.....


这样的模式类比于机器引擎,周而复始的工作和状态转化,这也是状态机的定语叫“机Machine”的原因


有了以上思路,我们尝试沟通UML 伪代码


fa0705dd3554c80bfa328f4f430712c1.png


态机设计模式的伪代码实现:


所谓的机器Machine维护了状态切换的上下文

机器对外暴露的行为,驱动机器的状态变更, 行为和状态是有因果关系的

机器到达特定的状态 只具备特定的行为,其他行为是不被允许的, 这在外面看,貌似是对象改变了原类的行为


下面使用golang实现了 状态机设计模型:这里你也可以看下golang 是如何体现OOP中的类继承、接口实现


goodMachine:状态变更上下文


package main
import (
 "fmt"
 "reflect"
)
type goodMachine struct {
 currentState state
 itemCount    int
 itemPrice    int
}
func newGoodMachine(itemCount, itemPrice int) *goodMachine {
 v := &goodMachine{
  itemCount: itemCount,
  itemPrice: itemPrice,
 }
 if itemCount <= 0 {
  v.setState(&noItemState{v}) // 实现state接口的是*noItemState 指针类型
 } else {
  v.setState(&hasItemState{v})
 }
 return v
}
func (v *goodMachine) setState(s state) {
 fmt.Println("enter state: ", reflect.TypeOf(s))
 v.currentState = s
}
func (v *goodMachine) requestItem() error {
 return v.currentState.requestItem()
}
func (v *goodMachine) addItem(count int) error {
 return v.currentState.addItem(count)
}
func (v *goodMachine) insertMoney(money int) error {
 return v.currentState.insertMoney(money)
}
func (v *goodMachine) incrementItemCount(count int) {
 v.itemCount += count
}
func (v goodMachine) dispenseItem() error {
 return v.currentState.dispenseItem()
}


自动售货机对外的行为,被委托给特定的state对象


state:自动售货机对外暴露的行为


package main
//  代表某种状态,能接受的某种动作
type state interface {
 addItem(count int) error
 requestItem() error
 insertMoney(money int) error
 dispenseItem() error
}


noItemState : 无商品


package main
import "fmt"
type noItemState struct {
 *goodMachine // 存在匿名类型 goodMachine,类型是*goodMachine
}
//  给自动售货机供货-----> 有货状态
func (i *noItemState) addItem(count int) error {
 i.incrementItemCount(count)
 i.setState(&hasItemState{i.goodMachine})
 return nil
}
func (i *noItemState) requestItem() error {
 return fmt.Errorf("item out of  stock")
}
func (i *noItemState) insertMoney(money int) error {
 return fmt.Errorf("item out of stock")
}
func (i *noItemState) dispenseItem() error {
 return fmt.Errorf("item out of stock")
}
// golang: 使用指针接受者实现了state接口的全部函数,那么隐式表明*noItemState 指针类型实现了State接口


注意:noItemState 结构体内定义了goodMachine, 就表明noItemState继承了goodMachine类


指针接受者
noItemState实现了state接口的所有函数,那么我们就说*noItemState实现了state接口。


hasItemState: 有商品


package main
import "fmt"
type hasItemState struct {
 *goodMachine
}
func (v *hasItemState) addItem(count int) error {
 v.incrementItemCount(count)
 return nil
}
// 有人选择了商品---> 没货状态/已经选定商品
func (v *hasItemState) requestItem() error {
 if v.goodMachine.itemCount == 0 {
  v.setState(&noItemState{v.goodMachine})
  return fmt.Errorf("no item present")
 }
 fmt.Print("item  requested\n")
 v.setState(&itemRequestedState{v.goodMachine})
 return nil
}
func (v *hasItemState) insertMoney(money int) error {
 return fmt.Errorf("Please select item first")
}
func (v *hasItemState) dispenseItem() error {
 return fmt.Errorf("Please select item first")
}


itemRequestedState:有人选定商品


package main
import "fmt"
type itemRequestedState struct {
 *goodMachine
}
func (i *itemRequestedState) addItem(count int) error {
 return fmt.Errorf("shopping is  in  process")
}
func (i *itemRequestedState) requestItem() error {
 return fmt.Errorf("item already requested")
}
// 付钱----> 已收钱状态
func (i *itemRequestedState) insertMoney(money int) error {
 if money < i.goodMachine.itemPrice {
  fmt.Errorf("insert money is less, please insert %d", i.goodMachine)
 }
 fmt.Println("money entered is ok")
 i.setState(&hasMoneyState{i.goodMachine})
 return nil
}
func (i *itemRequestedState) dispenseItem() error {
 return fmt.Errorf("please insert money first")
}


hasMoneyState:已付钱


package main
import "fmt"
type hasMoneyState struct {
 *goodMachine
}
func (i *hasMoneyState) addItem(count int) error {
 return fmt.Errorf("shopping is in process")
}
func (i *hasMoneyState) requestItem() error {
 return fmt.Errorf("shopping is in process")
}
func (i *hasMoneyState) insertMoney(money int) error {
 return fmt.Errorf("already pay money")
}
func (i *hasMoneyState) dispenseItem() error {
 fmt.Println("dispensing item")
 i.goodMachine.itemCount = i.goodMachine.itemCount - 1
 if i.goodMachine.itemCount == 0 {
  i.setState(&noItemState{i.goodMachine})
 } else {
  i.setState(&hasItemState{i.goodMachine})
 }
 return nil
}


main.go 执行


package main
import (
  "fmt"
  "log"
)
func main() {
  goodMachine := newGoodMachine(1, 10)
  err := goodMachine.requestItem()
  if err != nil {
    log.Fatalf(err.Error())
  }
  err = goodMachine.insertMoney(10)
  if err != nil {
    log.Fatalf(err.Error())
  }
  err = goodMachine.dispenseItem()
  if err != nil {
    log.Fatalf(err.Error())
  }
  fmt.Println()
  err = goodMachine.requestItem()
  if err != nil {
    log.Fatalf(err.Error())
  }
  err = goodMachine.insertMoney(10)
  if err != nil {
    log.Fatal(err.Error())
  }
  err = goodMachine.dispenseItem()
  if err != nil {
    log.Fatalf(err.Error())
  }
}


初始化了商品数量为1,价格为10 的自动售货机,连续掏10元钱买两次, 随时打印状态, 输出如下:


enter state:  *main.hasItemState
item  requested
enter state:  *main.itemRequestedState
money entered is ok
enter state:  *main.hasMoneyState     
dispensing item
enter state:  *main.noItemState       
2021/08/11 17:39:45 item out of  stock
exit status 1


状态机为什么定语是机器?Machine?


状态机表现了:


对象的状态受外界行为所影响,不断的切换,到达特定的状态又只能接受特定的行为, 真实生动的体现了机器Machine引擎的特征


本文示例亦是学习golang OOP编程的范例,golang 类继承、接口实现实在是太秀了

相关文章
|
4月前
|
测试技术
领域驱动设计问题之状态同步模型与状态机模型的主要区别是什么
领域驱动设计问题之状态同步模型与状态机模型的主要区别是什么
|
6月前
|
域名解析 负载均衡 网络协议
分布式与集群,二者区别是什么?
分布式与集群,二者区别是什么?
|
6月前
|
存储 Web App开发 运维
原来10张图就可以搞懂分布式链路追踪系统原理
原来10张图就可以搞懂分布式链路追踪系统原理
|
6月前
|
NoSQL 关系型数据库 MySQL
单机模拟集群(三主两从)
单机模拟集群(三主两从)
|
C语言
labview节点公式节点反馈节点表达节点属性节点
labview节点公式节点反馈节点表达节点属性节点
303 0
|
算法 调度
【操作系统篇】第五篇——调度(概念,层次,调度时机,切换与过程,方式,评价指标)
【操作系统篇】第五篇——调度(概念,层次,调度时机,切换与过程,方式,评价指标)
【操作系统篇】第五篇——调度(概念,层次,调度时机,切换与过程,方式,评价指标)
|
NoSQL 数据库 Redis
主从复制-工作流程(2)数据同步阶段(简)|学习笔记
快速学习主从复制-工作流程(2)数据同步阶段(简)
主从复制-工作流程(2)数据同步阶段(简)|学习笔记
|
Rust 自然语言处理 算法
【算法】2181. 合并零之间的节点(多语言实现)
给你一个链表的头节点 head ,该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val == 0 。 对于每两个相邻的 0 ,请你将它们之间的所有节点合并成一个节点,其值是所有已合并节点的值之和。然后将所有 0 移除,修改后的链表不应该含有任何 0 。 返回修改后链表的头节点 head 。
【算法】2181. 合并零之间的节点(多语言实现)
|
Kubernetes NoSQL Linux
K8S从懵圈到熟练:读懂这一篇,集群节点不下线
排查完全陌生的问题,完全不熟悉的系统组件,是售后工程师的一大工作乐趣,当然也是挑战。今天借这篇文章,跟大家分析一例这样的问题。排查过程中,需要理解一些自己完全陌生的组件,比如systemd和dbus。
K8S从懵圈到熟练:读懂这一篇,集群节点不下线