软件设计之状态机

============================================================================

原创作品。同意转载。

转载时请务必以超链接形式标明原始出处、以及本声明。

请注明转自:http://yunjianfei.iteye.com/blog/

============================================================================


一.状态机简介

软件设计中的状态机概念,通常是指有限状态机英语:finite-state machine。缩写FSM)又称有限状态自己主动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型

FSM(有限状态机)能够使用UML中的状态机图来表示。也能够使用类似以下格式的状态转移表等等。以下展示最常见的表示:当前状态(B)和事件(Y)的组合指示出下一个状态(C)。

    

状态转移表

当前状态 →
事件 ↓

状态 A

状态 B

状态 C

事件 X

事件 Y

状态 C

事件 Z

   

状态机有两个非常重要的概念: 状态、事件。下面是一个CD机的简单样例:

                                               

CD机状态转移表

状态 →
事件 ↓

 播放

暂停

停止

按播放键

播放

播放

按停止键

停止

停止

按暂停键

暂停

 通过这个表,我们能够非常直观的来理解状态机,例如以下:

1. 简单的CD机一般有三种状态: 播放、暂停、停止

2. 我们对CD机的操作。就是事件,简单来说有三种事件:按播放、停止、暂停按键。

3.在CD机不同的状态下,发生不同的事件(按不同的button)。触发的事情以及CD机下一步的状态(即状态转移)是不一样的。

4. 依照以上表格,假如CD机当前状态是“播放”,这时候,我们按播放键。它会保持“播放”状态,不会发生状态转移。假设按暂停键。则会触发状态转移。CD机状态转移为“暂停”状态。同理。按停止键会转移为停止状态。

二.状态机的实现方式

在软件的开发过程中,非常多情况下,我们都会涉及到“状态”这个概念,比方监控server的软件,server会有:‘开机’,‘关机’,‘负载过高’等状态。

执行任务的软件,对于每一个任务。会有“队列中”,“准备”。“执行”,“执行完成”,“失败”等状态。任务在不同的状态下,发生不同的事件,状态迁移和相应的逻辑都是不一样的,比方在“队列中”状态,发生事件“取消任务”,这时候仅仅须要把任务移出队列。而且状态变更为“失败”即可。相同,在“执行”状态时发生事件“取消任务”,则须要做非常多工作,比方回收执行任务的资源等。

通常状况下,假设状态非常少,可能不会涉及到状态机这个概念。

可是假设状态、事件非常多。假设设计不好状态机,软件开发到后期会非常吃力。

对于兴许的维护和升级也是问题。

状态机的实现主要有下面几种方式。这里我都以第一章中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 <stdio.h>
#include <stdlib.h>
#include <string.h>


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
", state_to_str[current_state]);
  current_state = RUNNING;
}

void do_change_stop()
{
  printf("CD Status from  '%s' to STOP
", state_to_str[current_state]);
  current_state = STOP;
}

void do_change_pause()
{
  printf("CD Status from  '%s' to pause
", state_to_str[current_state]);
  current_state = PAUSE;
}

int dispather(current_state, event) 
{
  switch (current_state) 
  {
    case STOP:
      switch(event) {
        case PRESS_RUNNING:
          do_change_running();
          break;
        case PRESS_PAUSE:
          do_change_pause();
          break;
        default:
          printf("CD's state not change
");
          break;
      }
      break;
    case PAUSE:
      switch(event) {
        case PRESS_RUNNING:
          do_change_running();
          break;
        case PRESS_STOP:
          do_change_stop();
          break;
        default:
          printf("CD's state not change
");
          break;
      }
      break;
    case RUNNING:
      switch(event) {
        case PRESS_PAUSE:
          do_change_pause();
          break;
        case PRESS_STOP:
          do_change_stop();
          break;
        default:
          printf("CD's state not change
");
          break;
      }
      break;
    default:
      printf("Error! no such status!
");
      break;
  }
}


int main ()
{
  char ch = '0';

  printf ("请输入数字操作CD机(0:RUNNING, 1:PAUSE, 2:STOP):
");
  while (1)
  {
    ch = getchar();
    if (ch == '
')
    {
    } 
    else if ((ch < '0') || (ch > '3'))
    {
      printf ("非法输入。请又一次输入!
");
      continue;
    }
    else
    {
      char event = ch;
      dispather(current_state, event);
      printf ("请输入数字操作CD机(0:RUNNING, 1:PAUSE, 2:STOP):
");
    }
  }
  return 0;
}

3. 函数指针法。这样的方法是我最经常使用的,也是最喜欢的。

所以这里我会分别贴出C和python的代码,具体解说。

 

细致观察第一章中的“CD机状态转换图”, 它事实上就是一个二维矩阵结构,二维矩阵结构相应到数据结构中,无非就是二维数组。在状态转换时,都有相应的逻辑处理,所以,我们全然能够使用 “二维函数指针”来实现状态转换图。

这里我没有使用二维数组,使用了结构体数组,这样的实现更为直观。

 

假设有新的状态增加。仅仅须要在state_mechine这个数组中增加新的状态,事件以及处理函数就可以。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef enum
{
  STOP = 0,
  RUNNING,
  PAUSE,
  MAX_STATE,
} CD_STATE;


typedef enum
{
  PRESS_RUNNING = 0,
  PRESS_PAUSE,
  PRESS_STOP,
  MAX_EVENT,
} CD_EVENT;

char state_to_str[3][100] = {"STOP", "RUNNING", "PAUSE"};


struct CD_STATE_MECHINE 
{
  int state;
  int event;
  void (*func)(unsigned char *);
};

void do_change_running(unsigned char * user_data);
void do_change_stop(unsigned char * user_data);
void do_change_pause(unsigned char * user_data);

struct CD_STATE_MECHINE state_mechine[] = {
  {RUNNING, PRESS_RUNNING, NULL},
  {RUNNING, PRESS_STOP, do_change_stop},
  {RUNNING, PRESS_PAUSE, do_change_pause},
  {PAUSE, PRESS_RUNNING, do_change_running},
  {PAUSE, PRESS_STOP, do_change_stop},
  {PAUSE, PRESS_PAUSE, NULL},
  {STOP, PRESS_RUNNING, do_change_running},
  {STOP, PRESS_STOP, NULL},
  {STOP, PRESS_PAUSE, do_change_pause},
  {-1, -1, NULL},
};


//全局变量。用来存储CD当前状态
int current_state = STOP;

void do_change_running(unsigned char * user_data)
{
  printf("CD Status from  %s to RUNING
", state_to_str[current_state]);
  current_state = RUNNING;
}

void do_change_stop(unsigned char * user_data)
{
  printf("CD Status from  '%s' to STOP
", state_to_str[current_state]);
  current_state = STOP;
}

void do_change_pause(unsigned char * user_data)
{
  printf("CD Status from  '%s' to pause
", state_to_str[current_state]);
  current_state = PAUSE;
}

int dispather(current_state, event) 
{
  int i = 0;
  for(i = 0; state_mechine[i].state != -1; i++)
  {
    if (current_state == state_mechine[i].state && event == state_mechine[i].event)
    {
      void (*func)(unsigned char *);
      func = state_mechine[i].func;
      if (func != NULL)
      {
        func(NULL);
      }
      else
      {
        printf("state not change!
");
      }
      break;
    }
  }
}


int main ()
{
  char ch = '0';

  printf ("请输入数字操作CD机(0:RUNNING, 1:PAUSE, 2:STOP):
");
  while (1)
  {
    ch = getchar();
    if (ch == '
')
    {
    } 
    else if ((ch < '0') || (ch > '3'))
    {
      printf ("非法输入。请又一次输入!
");
      continue;
    }
    else
    {
      int event = ch - '0';
      dispather(current_state, event);
      printf ("请输入数字操作CD机(0:RUNNING, 1:PAUSE, 2:STOP):
");
    }
  }
  return 0;
}

临时写到这里。回头儿有空了,再补上python版本号的状态机实现。

原文地址:https://www.cnblogs.com/gcczhongduan/p/5341998.html