一.状态机简单介绍
软件设计中的状态机概念,一般是指有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
FSM(有限状态机)可以使用UML中的状态机图来表示。也可以使用类似以下格式的状态转移表等等。下面展示最常见的表示:当前状态(B)和事件(Y)的组合指示出下一个状态(C)。
| 状态转移表 |
|||
| 当前状态 → |
状态 A |
状态 B |
状态 C |
| 事件 X |
… |
… |
… |
| 事件 Y |
… |
状态 C |
… |
| 事件 Z |
… |
… |
… |
状态机有两个很重要的概念: 状态、事件。以下是一个CD机的简单例子:
| CD机状态转移表 |
|||
| 状态 → |
播放 |
暂停 |
停止 |
| 按播放键 |
… |
播放 |
播放 |
| 按停止键 |
停止 |
停止 |
… |
| 按暂停键 |
暂停 |
… |
… |
通过这个表,我们可以很直观的来理解状态机,如下:
1. 简单的CD机一般有三种状态: 播放、暂停、停止
2. 我们对CD机的操作,就是事件,简单来说有三种事件:按播放、停止、暂停按键。
3.在CD机不同的状态下,发生不同的事件(按不同的按钮),触发的事情以及CD机下一步的状态(即状态转移)是不一样的。
4. 按照以上表格,假如CD机当前状态是“播放”,这时候,我们按播放键,它会保持“播放”状态,不会发生状态转移,如果按暂停键,则会触发状态转移,CD机状态转移为“暂停”状态。同理,按停止键会转移为停止状态。
二.状态机的实现方式
在软件的开发过程中,很多情况下,我们都会涉及到“状态”这个概念,比如监控服务器的软件,服务器会有:‘开机’,‘关机’,‘负载过高’等状态。
执行任务的软件,对于每个任务,会有“队列中”,“准备”,“运行”,“运行完毕”,“失败”等状态,任务在不同的状态下,发生不同的事件,状态迁移和对应的逻辑都是不一样的,比如在“队列中”状态,发生事件“取消任务”,这时候只需要把任务移出队列,并且状态变更为“失败”就行。同样,在“运行”状态时发生事件“取消任务”,则需要做很多工作,比如回收运行任务的资源等。
通常状况下,如果状态很少,可能不会涉及到状态机这个概念。但是如果状态、事件很多,如果设计不好状态机,软件开发到后期会非常吃力。对于后续的维护和升级也是问题。
状态机的实现主要有以下几种方式,这里我都以第一章中CD机的例子来做简单实现说明。
注:这里我主要以python或者C语言写的代码来说明,实际使用中,用任何语言都行,关键是逻辑思维。
1. if...else..... PS:最土最繁琐的方式(个人极不喜欢)
#!/usr/bin/python
class CDStatus:
RUNNING = 0
STOP = 1
PAUSE = 2
class CDEvent:
PRESS_RUNNING = 0
PRESS_STOP = 1
PRESS_PAUSE = 2
def do_change_stop():
#TODO someting and change CD status to STOP
print "Chang CD to 'stop' status"
def do_change_running():
#TODO someting and change CD status to RUNNING
print "Chang CD to 'running' status"
def do_change_pause():
#TODO someting and change CD status to PAUSE
print "Chang CD to 'pause' status"
def dispather(curr_status, event):
if curr_status == CDStatus.RUNNING:
if event == CDEvent.PRESS_STOP:
do_change_stop()
elif event == CDEvent.PRESS_PAUSE:
do_change_pause()
elif curr_status == CDStatus.STOP:
if event == CDEvent.PRESS_RUNNING:
do_change_running()
elif event == CDEvent.PRESS_PAUSE:
do_change_pause()
elif curr_status == CDStatus.PAUSE:
if event == CDEvent.PRESS_RUNNING:
do_change_running()
elif event == CDEvent.PRESS_STOP:
do_change_stop()
else:
print "error!"
def main():
current_status = CDStatus.STOP
event = CDEvent.PRESS_RUNNING
dispather(current_status, event)
return
if __name__ == "__main__":
main()
可以看到,这个例子极为繁琐,if...else还需要嵌套2层,外层判断状态,里层判断事件,最终通过当前状态和发生的事件,对应“CD机状态迁移表”, 处理事件以及转移状态。
这种方式非常不灵活,因为每增加一个状态,都要加一堆if ..else的判定。
2. switch...case..... python中没有switch语法,可以用dict来代替,这里我用大体的C语言来描述。
下面的代码可以直接编译运行,在dispather中,嵌套了2层switch,类似上面if..else的结构,只不过换成了switch.
#include#include #include typedef enum { STOP = 0, RUNNING, PAUSE, } CD_STATE; typedef enum { PRESS_RUNNING = '0', PRESS_PAUSE = '1', PRESS_STOP = '2', } CD_EVENT; char state_to_str[3][100] = {"STOP", "RUNNING", "PAUSE"}; //全局变量,用来存储CD当前状态 int current_state = STOP; void do_change_running() { printf("CD Status from %s to RUNING\n", state_to_str[current_state]); current_state = RUNNING; } void do_change_stop() { printf("CD Status from '%s' to STOP\n", state_to_str[current_state]); current_state = STOP; } void do_change_pause() { printf("CD Status from '%s' to pause\n", state_to_str[current_state]); current_state = PAUSE; } int dispather(current_state, event) { switch (current_state) { case STOP: switch(event) { case P