golang 自定义类型不能使用nil_使用GoLang实现自定义状态机

ba1c247a99da7f6cacae21ea15d938a7.png

微信公众号:[double12gzh]

关注容器技术、关注Kubernetes。问题或建议,请公众号留言。

1. 背景

在计算机领域中,状态机是一个比较基础的概念。在我们的日常生活中,我们可以看到许多状态机的例子,如:交通信息号灯、电梯、自动售货机等。

基于FSM的编程也是一个强大的工具,可以对复杂的状态转换进行建模,它可以大大简化我们的程序。

2. 什么是状态机

有限状态机(FSM)或简称状态机,是一种计算的数学模型。它是一个抽象的机器,在任何时间都可以处于有限的状态之一。FSM可以根据一些输入从一个状 态变为另一个状态;从一个状态到另一个状态的变化称为转换。

一个FSM由三个关键要素组成:初始状态所有可能状态的列表触发状态转换的输入

下面我们以旋转门作为FSM建模的一个简单例子(来自Wikipedia)

ba1c247a99da7f6cacae21ea15d938a7.png

和其他FSM一样,转门的状态机有三个元素:

•它的初始状态是 "锁定"•它有两种可能的状态。"锁定 "和 "解锁"•两个输入将触发状态变化。"推 "和 "硬币"

3. 实现状态机

接下来,我将建立一个模拟旋转门行为的命令行程序。当程序启动时,它会提示用户输入一些命令,然后它将根据输入的命令改变其状态。

3.1 版本1 简单直接

package mainimport (    "bufio"    "fmt"    "log"    "os"    "strings")// 旋转门状态type State uint32const (    Locked State = iota    Unlocked)// 相关的命令const (    CmdCoin = "coin"    CmdPush = "push")func main() {    state := Locked    reader := bufio.NewReader(os.Stdin)    prompt(state)    for {        cmd, err := reader.ReadString('\n')        if err != nil {            log.Fatalln(err)        }        cmd = strings.TrimSpace(cmd)        switch state {        case Locked:            if cmd == CmdCoin {                fmt.Println("解锁, 请通行")                state = Unlocked            } else if cmd == CmdPush {                fmt.Println("禁止通行,请先解锁")            } else {                fmt.Println("命令未知,请重新输入")            }        case Unlocked:            if cmd == CmdCoin {                fmt.Println("大兄弟,门开着呢,别浪费钱了")            } else if cmd == CmdPush {                fmt.Println("请通行,通行之后将会关闭")                state = Locked            } else {                fmt.Println("命令未知,请重新输入")            }        }    }}func prompt(s State) {    m := map[State]string{        Locked:   "Locked",        Unlocked: "Unlocked",    }    fmt.Printf("当前的状态是: [%s], 请输入命令: [coin|push]\n", m[s])}

说明:

•首先定义两个状态Locked/Unlocked和两个支持的命令CmdCoin/CmdPush•在main函数中设定了旋转门的初始状态为Locked•后面启动一个无限循环,等待用户输入命令,并根据不同的状态处理不同的命令

问题与优化:

•我们必须处理每个状态的未知命令,这可以通过小的重构来改进。•如果我们把状态转换的逻辑提取到一个函数中,程序的表达能力会更强。

3.2 版本2 重构优化

...func main() {    ...    for {        cmd, err := reader.ReadString('\n')        if err != nil {            log.Fatalln(err)        }        state = step(state, strings.TrimSpace(cmd))    }}func step(state State, cmd string) State {    if cmd != CmdCoin && cmd != CmdPush {        fmt.Println("未知命令,请重新输入")        return state    }    switch state {    case Locked:        if cmd == CmdCoin {            fmt.Println("已解锁,请通行")            state = Unlocked        } else {            fmt.Println("禁止通行,请先解锁")        }    case Unlocked:        if cmd == CmdCoin {            fmt.Println("大兄弟,别浪费钱了,现在已经解锁了")        } else {            fmt.Println("请通行,通行之后将会关闭")            state = Locked        }    }    return state}...

实现上,一个状态机通常会使用状态转换表来表示,如下:

10aa6d1c15f18b724dee723d07e67561.png

3.3 版本3 状态转换表

通过上面的分析下,针对上述实现再次优化,这次引入状态转换表的实现

...func main() {    ...    for {        // 读取用户的输入        cmd, err := reader.ReadString('\n')        if err != nil {            log.Fatalln(err)        }                // 获取状态转换表中的值        tupple := CommandStateTupple{strings.TrimSpace(cmd), state}        if f := StateTransitionTable[tupple]; f == nil {            fmt.Println("未知命令,请重新输入")        } else {            f(&state)        }    }}// CommandStateTupple 用于存放状态转换表的结构体type CommandStateTupple struct {    Command string    State   State}// TransitionFunc 状态转移方程type TransitionFunc func(state *State)// StateTransitionTable 状态转换表var StateTransitionTable = map[CommandStateTupple]TransitionFunc{    {CmdCoin, Locked}: func(state *State) {        fmt.Println("已解锁,请通行")        *state = Unlocked    },    {CmdPush, Locked}: func(state *State) {        fmt.Println("禁止通行,请先行解锁")    },    {CmdCoin, Unlocked}: func(state *State) {        fmt.Println("大兄弟,已解锁了,别浪费钱了")    },    {CmdPush, Unlocked}: func(state *State) {        fmt.Println("请尽快通行,通行后将自动上锁")        *state = Locked    },}...

采用这种方法,所有可能的转换都列在表格中。它易于维护和理解。如果需要一个新的转换,只需增加一个表项。 由于FSM是一个抽象的机器,我们可以更进一步,以面向对象的方式实现它。

3.4 版本4 通过class来抽象

这里我们将会引入一个新的类Turnstile,这个类有一个属性State和一个方法ExecuteCmd。当需要进行状态转换时,就调用ExecuteCmd, 并且ExecuteCmd是唯一能触发状态发生转换的途径。

类图如下

f599a5616f2b7c027d71045abacc8216.png

完整的代码实现如下:

package mainimport (    "bufio"    "fmt"    "log"    "os"    "strings")type State uint32const (    Locked State = iota    Unlocked)const (    CmdCoin = "coin"    CmdPush = "push")type Turnstile struct {    State State}// ExecuteCmd 执行命令func (p *Turnstile) ExecuteCmd(cmd string) {    tupple := CmdStateTupple{strings.TrimSpace(cmd), p.State}    if f := StateTransitionTable[tupple]; f == nil {        fmt.Println("unknown command, try again please")    } else {        f(&p.State)    }}func main() {    machine := &Turnstile{State: Locked}    prompt(machine.State)    reader := bufio.NewReader(os.Stdin)    for {        cmd, err := reader.ReadString('\n')        if err != nil {            log.Fatalln(err)        }        machine.ExecuteCmd(cmd)    }}type CmdStateTupple struct {    Cmd   string    State State}type TransitionFunc func(state *State)var StateTransitionTable = map[CmdStateTupple]TransitionFunc{    {CmdCoin, Locked}: func(state *State) {        fmt.Println("已解锁,请通行")        *state = Unlocked    },    {CmdPush, Locked}: func(state *State) {        fmt.Println("禁止通行,请先解锁")    },    {CmdCoin, Unlocked}: func(state *State) {        fmt.Println("大兄弟,不要浪费钱了")    },    {CmdPush, Unlocked}: func(state *State) {        fmt.Println("请尽快通行,然后将会锁定")        *state = Locked    },}func prompt(s State) {    m := map[State]string{        Locked:   "Locked",        Unlocked: "Unlocked",    }    fmt.Printf("当前的状态是: [%s], 请输入命令:[coin|push]\n", m[s])}

运行一下上面的代码,可以看到如下的输出:

F:\hello>go run main.go当前的状态是: [Locked], 请输入命令:[coin|push]coin已解锁,请通行push请尽快通行,然后将会锁定fuckunknown command, try again pleasepush禁止通行,请先解锁push禁止通行,请先解锁coin已解锁,请通行push请尽快通行,然后将会锁定push禁止通行,请先解锁

4. 小结

在这个故事中,我们介绍了FSM的概念,并建立了一个基于FSM的程序,同时,我们提供了四个版本的实现方式来实现FSM:

•v1,以直接的形式实现FSM。•v2,做一些重构以减少代码重复。•v3、引入状态转换表•v4,用OOP重构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值