軟件設計之狀態機

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

原創作品,允許轉載。轉載時請務必以超鏈接形式標明原始出處、以及本聲明。

請註明轉自: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機不同的狀態下,發生不同的事件(按不同的按鈕),觸發的事情以及CD機下一步的狀態(即狀態轉移)是不一樣的。

4. 按照以上表格,假如CD機當前狀態是“播放”,這時候,我們按播放鍵,它會保持“播放”狀態,不會發生狀態轉移,如果按暫停鍵,則會觸發狀態轉移,CD機狀態轉移爲“暫停”狀態。同理,按停止鍵會轉移爲停止狀態。

 

二.狀態機的實現方式

在軟件的開發過程中,很多情況下,我們都會涉及到“狀態”這個概念,比如監控服務器的軟件,服務器會有:‘開機’,‘關機’,‘負載過高’等狀態。

執行任務的軟件,對於每個任務,會有“隊列中”,“準備”,“運行”,“運行完畢”,“失敗”等狀態,任務在不同的狀態下,發生不同的事件,狀態遷移和對應的邏輯都是不一樣的,比如在“隊列中”狀態,發生事件“取消任務”,這時候只需要把任務移出隊列,並且狀態變更爲“失敗”就行。同樣,在“運行”狀態時發生事件“取消任務”,則需要做很多工作,比如回收運行任務的資源等。

 

通常狀況下,如果狀態很少,可能不會涉及到狀態機這個概念。但是如果狀態、事件很多,如果設計不好狀態機,軟件開發到後期會非常吃力。對於後續的維護和升級也是問題。

 

狀態機的實現主要有以下幾種方式,這裏我都以第一章中CD機的例子來做簡單實現說明。

 

注:這裏我主要以python或者C語言寫的代碼來說明,實際使用中,用任何語言都行,關鍵是邏輯思維。

 

1.  if...else.....       PS:最土最繁瑣的方式(個人極不喜歡)

 

#!/usr/bin/python

##可以對應爲C/C++/java中的enum類型,標示CD狀態
class CDStatus:
    RUNNING = 0
    STOP = 1
    PAUSE = 2
##同上,enum類型,標示CD的事件
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"

##對應CD機狀態遷移圖
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\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 PRESS_RUNNING:
          do_change_running();
          break;
        case PRESS_PAUSE:
          do_change_pause();
          break;
        default:
          printf("CD's state not change\n");
          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\n");
          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\n");
          break;
      }
      break;
    default:
      printf("Error! no such status!\n");
      break;
  }
}


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

  printf ("請輸入數字操作CD機(0:RUNNING, 1:PAUSE, 2:STOP):\n");
  while (1)
  {
    ch = getchar();
    if (ch == '\n')
    {
    } 
    else if ((ch < '0') || (ch > '3'))
    {
      printf ("非法輸入,請重新輸入!\n");
      continue;
    }
    else
    {
      char event = ch;
      dispather(current_state, event);
      printf ("請輸入數字操作CD機(0:RUNNING, 1:PAUSE, 2:STOP):\n");
    }
  }
  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\n", state_to_str[current_state]);
  current_state = RUNNING;
}

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

void do_change_pause(unsigned char * user_data)
{
  printf("CD Status from  '%s' to pause\n", 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!\n");
      }
      break;
    }
  }
}


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

  printf ("請輸入數字操作CD機(0:RUNNING, 1:PAUSE, 2:STOP):\n");
  while (1)
  {
    ch = getchar();
    if (ch == '\n')
    {
    } 
    else if ((ch < '0') || (ch > '3'))
    {
      printf ("非法輸入,請重新輸入!\n");
      continue;
    }
    else
    {
      int event = ch - '0';
      dispather(current_state, event);
      printf ("請輸入數字操作CD機(0:RUNNING, 1:PAUSE, 2:STOP):\n");
    }
  }
  return 0;
}

  

暫時寫到這裏,回頭兒有空了,再不上python版本的狀態機實現。

 
發佈了19 篇原創文章 · 獲贊 16 · 訪問量 8334
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章