状态模式使用的相对较少,主要是因为会引入大量的状态类,导致代码比较难维护。但是合适的场景使用状态模式,可以把复杂的判断逻辑简化。
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,如果不使用状态模式,写出来的样子是这样的:
1 | func E1() { |
单看伪代码可能觉得还好,但是细想一想,如果动作执行比较复杂,代码是不是就很丑了。后期如果状态或者事件变更,如何确保每一处都进行了更改?这时候状态模式便起作用了。
我们创建四个类,如UML中的ConcreteStateA、ConcreteStateB、ConcreteStateC、ConcreteStateD,分别代表四种状态。每个状态类中有4个Handle函数,分别对应4个事件。通过这种方式,将糅杂在一起的逻辑进行了拆分,代码看起来优雅了很多。
2.应用场景
这么多年都没有用过状态模式,使用场景确实有点少。
实际业务场景中做过跨境履约单的状态机,履约单需要经历接单、清关中、清关成功、发货等状态。这种场景相对简单,状态只能单方向流转、单独的接口触发指定状态流转到下一个状态,复杂的部分在于下一个状态可能有多个,有的可以跳过。这种情况下,使用数组维护状态机,比状态模式要好。
如果状态多、动作执行逻辑复杂,那使用状态模式还是挺合理的,一般游戏中使用状态模式相对多一些。本次借用《设计模式之美》里超级马里奥的例子,使用超级马里奥介绍实在是太合适了,一是因为马里奥有多种状态、多种触发事件,特别适合使用状态模式;二是超级马里奥大家都玩过,业务情况大家都熟悉。为了帮助大家回忆,我找了马里奥全系列变身形态https://zhuanlan.zhihu.com/p/250931383。
3.代码实现
马里奥状态有小马里奥(Small Mario)、超级马里奥(Super Mario)、斗篷马里奥(Cape Mario),小马里奥吃了蘑菇变为超级马里奥,小马里奥和超级马里奥获得斗篷变成斗篷马里奥,超级马里奥和斗篷马里奥碰到怪物变成小马里奥。
1 | package main |
输出:
➜ myproject go run main.go
小马里奥
——————-获得蘑菇
超级马里奥
——————-获得斗篷
斗篷马里奥
——————-遇到怪兽
小马里奥
总结
仔细看上面的代码
- 对事件触发状态的转移及动作的执行的改动会很简单
- 可快速增加新的事件
- 增加新的状态也方便,只需添加新的状态类,少量修改已有代码
坏处就是类特别多,类里的函数也会特别多,即使这些函数根本无用。不过能获得更好的扩展性,还是值得的。