Go设计模式(22)-状态模式

简介: 状态模式使用的相对较少,主要是因为会引入大量的状态类,导致代码比较难维护。但是合适的场景使用状态模式,可以把复杂的判断逻辑简化。

状态模式使用的相对较少,主要是因为会引入大量的状态类,导致代码比较难维护。但是合适的场景使用状态模式,可以把复杂的判断逻辑简化。

UML类图位置:https://www.processon.com/view/link/60d29bf3e401fd49502afd25

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/22status.go

1.定义

1.1状态模式

状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

UML:

图片

1.2分析

状态机有3个组成部分:状态(State)、事件(Event)、动作(Action)。事件触发状态的转移及动作的执行。

只看定义和UML,可能比较难理解使用状态模式有什么好处,举个例子就清晰了。

假设有四种状态A、B、C、D,同时有四种触发事件E1、E2、E3、E4,如果不使用状态模式,写出来的样子是这样的:

func E1() {
   if status == "A" {
      //状态迁移+动作执行
   } else if status == "B" {
      //状态迁移+动作执行
   } else if status == "C" {
      //状态迁移+动作执行
   } else if status == "D" {
      //状态迁移+动作执行
   }
}

func E2() {
   if status == "A" {
      //状态迁移+动作执行
   } else if status == "B" {
      //状态迁移+动作执行
   } else if status == "C" {
      //状态迁移+动作执行
   } else if status == "D" {
      //状态迁移+动作执行
   }
}

单看伪代码可能觉得还好,但是细想一想,如果动作执行比较复杂,代码是不是就很丑了。后期如果状态或者事件变更,如何确保每一处都进行了更改?这时候状态模式便起作用了。

我们创建四个类,如UML中的ConcreteStateA、ConcreteStateB、ConcreteStateC、ConcreteStateD,分别代表四种状态。每个状态类中有4个Handle函数,分别对应4个事件。通过这种方式,将糅杂在一起的逻辑进行了拆分,代码看起来优雅了很多。

2.应用场景

这么多年都没有用过状态模式,使用场景确实有点少。

实际业务场景中做过跨境履约单的状态机,履约单需要经历接单、清关中、清关成功、发货等状态。这种场景相对简单,状态只能单方向流转、单独的接口触发指定状态流转到下一个状态,复杂的部分在于下一个状态可能有多个,有的可以跳过。这种情况下,使用数组维护状态机,比状态模式要好。

如果状态多、动作执行逻辑复杂,那使用状态模式还是挺合理的,一般游戏中使用状态模式相对多一些。本次借用《设计模式之美》里超级马里奥的例子,使用超级马里奥介绍实在是太合适了,一是因为马里奥有多种状态、多种触发事件,特别适合使用状态模式;二是超级马里奥大家都玩过,业务情况大家都熟悉。为了帮助大家回忆,我找了马里奥全系列变身形态https://zhuanlan.zhihu.com/p/250931383

3.代码实现

图片

马里奥状态有小马里奥(Small Mario)、超级马里奥(Super Mario)、斗篷马里奥(Cape Mario),小马里奥吃了蘑菇变为超级马里奥,小马里奥和超级马里奥获得斗篷变成斗篷马里奥,超级马里奥和斗篷马里奥碰到怪物变成小马里奥。

package main

import "fmt"

type Mario struct {
  score  int64
  status MarioStatus
}

type MarioStatus interface {
  Name()
  ObtainMushroom()
  ObtainCape()
  MeetMonster()
  SetMario(mario *Mario)
}

/**
 * @Author: Jason Pang
 * @Description: 小马里奥
 */
type SmallMarioStatus struct {
  mario *Mario
}

/**
 * @Author: Jason Pang
 * @Description: 设置马里奥
 * @receiver s
 * @param mario
 */
func (s *SmallMarioStatus) SetMario(mario *Mario) {
  s.mario = mario
}

func (s *SmallMarioStatus) Name() {
  fmt.Println("小马里奥")
}

/**
 * @Author: Jason Pang
 * @Description: 获得蘑菇变为超级马里奥
 * @receiver s
 */
func (s *SmallMarioStatus) ObtainMushroom() {
  s.mario.status = &SuperMarioStatus{
    mario: s.mario,
  }
  s.mario.score += 100
}

/**
 * @Author: Jason Pang
 * @Description: 获得斗篷变为斗篷马里奥
 * @receiver s
 */
func (s *SmallMarioStatus) ObtainCape() {
  s.mario.status = &CapeMarioStatus{
    mario: s.mario,
  }
  s.mario.score += 200
}

/**
 * @Author: Jason Pang
 * @Description: 遇到怪兽减100
 * @receiver s
 */
func (s *SmallMarioStatus) MeetMonster() {
  s.mario.score -= 100
}

/**
 * @Author: Jason Pang
 * @Description: 超级马里奥
 */

type SuperMarioStatus struct {
  mario *Mario
}

/**
 * @Author: Jason Pang
 * @Description: 设置马里奥
 * @receiver s
 * @param mario
 */
func (s *SuperMarioStatus) SetMario(mario *Mario) {
  s.mario = mario
}

func (s *SuperMarioStatus) Name() {
  fmt.Println("超级马里奥")
}

/**
 * @Author: Jason Pang
 * @Description: 获得蘑菇无变化
 * @receiver s
 */
func (s *SuperMarioStatus) ObtainMushroom() {

}

/**
 * @Author: Jason Pang
 * @Description:获得斗篷变为斗篷马里奥
 * @receiver s
 */
func (s *SuperMarioStatus) ObtainCape() {
  s.mario.status = &CapeMarioStatus{
    mario: s.mario,
  }
  s.mario.score += 200
}

/**
 * @Author: Jason Pang
 * @Description: 遇到怪兽变为小马里奥
 * @receiver s
 */
func (s *SuperMarioStatus) MeetMonster() {
  s.mario.status = &SmallMarioStatus{
    mario: s.mario,
  }
  s.mario.score -= 200
}

/**
 * @Author: Jason Pang
 * @Description: 斗篷马里奥
 */
type CapeMarioStatus struct {
  mario *Mario
}

/**
 * @Author: Jason Pang
 * @Description: 设置马里奥
 * @receiver s
 * @param mario
 */
func (c *CapeMarioStatus) SetMario(mario *Mario) {
  c.mario = mario
}

func (c *CapeMarioStatus) Name() {
  fmt.Println("斗篷马里奥")
}

/**
 * @Author: Jason Pang
 * @Description:获得蘑菇无变化
 * @receiver c
 */
func (c *CapeMarioStatus) ObtainMushroom() {

}

/**
 * @Author: Jason Pang
 * @Description: 获得斗篷无变化
 * @receiver c
 */
func (c *CapeMarioStatus) ObtainCape() {

}

/**
 * @Author: Jason Pang
 * @Description: 遇到怪兽变为小马里奥
 * @receiver c
 */
func (c *CapeMarioStatus) MeetMonster() {
  c.mario.status = &SmallMarioStatus{
    mario: c.mario,
  }
  c.mario.score -= 200
}
func main() {
  mario := Mario{
    status: &SmallMarioStatus{},
    score:  0,
  }
  mario.status.SetMario(&mario)

  mario.status.Name()
  fmt.Println("-------------------获得蘑菇\n")
  mario.status.ObtainMushroom()

  mario.status.Name()
  fmt.Println("-------------------获得斗篷\n")
  mario.status.ObtainCape()

  mario.status.Name()
  fmt.Println("-------------------遇到怪兽\n")
  mario.status.MeetMonster()

  mario.status.Name()
}

输出:

➜ myproject go run main.go

小马里奥

-------------------获得蘑菇

超级马里奥

-------------------获得斗篷

斗篷马里奥

-------------------遇到怪兽

小马里奥

总结

仔细看上面的代码

  • 对事件触发状态的转移及动作的执行的改动会很简单
  • 可快速增加新的事件
  • 增加新的状态也方便,只需添加新的状态类,少量修改已有代码

坏处就是类特别多,类里的函数也会特别多,即使这些函数根本无用。不过能获得更好的扩展性,还是值得的。

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

往期文章回顾:

  1. 设计模式
  2. 招聘
  3. 思考
  4. 存储
  5. 算法系列
  6. 读书笔记
  7. 小工具
  8. 架构
  9. 网络
  10. Go语言
相关文章
|
2月前
|
设计模式 Java 测试技术
Java设计模式-状态模式(18)
Java设计模式-状态模式(18)
|
3月前
|
设计模式 网络协议 Java
【十五】设计模式~~~行为型模式~~~状态模式(Java)
文章详细介绍了状态模式(State Pattern),这是一种对象行为型模式,用于处理对象在其内部状态改变时的行为变化。文中通过案例分析,如银行账户状态管理和屏幕放大镜工具,展示了状态模式的应用场景和设计方法。文章阐述了状态模式的动机、定义、结构、优点、缺点以及适用情况,并提供了Java代码实现和测试结果。状态模式通过将对象的状态和行为封装在独立的状态类中,提高了系统的可扩展性和可维护性。
【十五】设计模式~~~行为型模式~~~状态模式(Java)
|
3月前
|
设计模式 Go
go 设计模式之观察者模式
go 设计模式之观察者模式
|
4月前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
72 8
|
4月前
|
设计模式 JavaScript Go
js设计模式【详解】—— 状态模式
js设计模式【详解】—— 状态模式
80 7
|
5月前
|
设计模式
状态模式-大话设计模式
状态模式-大话设计模式
|
5月前
|
设计模式 存储
行为设计模式之状态模式
行为设计模式之状态模式
|
6月前
|
设计模式 Go
[设计模式 Go实现] 结构型~享元模式
[设计模式 Go实现] 结构型~享元模式
|
6月前
|
设计模式 Go API
[设计模式 Go实现] 结构型~外观模式
[设计模式 Go实现] 结构型~外观模式
|
6月前
|
设计模式 JavaScript Java
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式