有没有典型的状态机实施模式?

时间:2020-03-06 14:43:09  来源:igfitidea点击:

我们需要在C中实现一个简单的状态机。
标准的switch语句是最好的方法吗?
我们有一个当前状态(状态)和一个转换触发器。

switch(state)
{
  case STATE_1:
     state = DoState1(transition);
     break;
  case STATE_2:
     state = DoState2(transition);
     break;
}
...
DoState2(int transition)
{
   // Do State Work
   ...
   if(transition == FROM_STATE_2) {
     // New state when doing STATE 2 -> STATE 2
   }
   if(transition == FROM_STATE_1) {
    // New State when moving STATE 1 -> STATE 2
   }
   return new_state;
}

对于简单的状态机,是否有更好的方法

编辑:
对于C ++,我认为Boost Statechart库可能是要走的路。但是,它对C并没有帮助。让我们专注于C用例。

解决方案

Boost具有状态图库。 http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html

不过,我无法说出它的用途。本人尚未使用(尚未使用)

以我的经验,使用" switch"语句是处理多种可能状态的标准方法。尽管令我感到惊讶的是,我们正在将过渡值传递给每个状态的处理。我认为状态机的全部意义在于每个状态都执行一个动作。然后,下一个动作/输入确定要转换到的新状态。因此,我希望每个状态处理功能都能立即执行固定的进入状态的操作,然后再决定是否需要转换到另一个状态。

在C ++中,请考虑"状态"模式。

还有一个逻辑网格,随着状态机的变大,它更易于维护

对于简单的情况,可以使用开关样式方法。我发现过去有效的方法也是处理过渡:

static int current_state;    // should always hold current state -- and probably be an enum or something

void state_leave(int new_state) {
    // do processing on what it means to enter the new state
    // which might be dependent on the current state
}

void state_enter(int new_state) {
    // do processing on what is means to leave the current atate
    // might be dependent on the new state

    current_state = new_state;
}

void state_process() {
    // switch statement to handle current state
}

我对boost库一无所知,但是这种方法非常简单,不需要任何外部依赖,并且易于实现。

对于状态模式,本文是一篇不错的文章(尽管它是C ++,而不是C)。

如果我们可以把手放在" Head First Design Patterns"一书上,那么说明和示例将非常清楚。

有一本书的标题为《 C / C ++中的实用状态图》。
但是,对于我们所需要的来说,它太繁重了。

我更喜欢对大多数状态机使用表驱动的方法:

typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t;
typedef struct instance_data instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_initial( instance_data_t *data );
state_t do_state_foo( instance_data_t *data );
state_t do_state_bar( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
    do_state_initial, do_state_foo, do_state_bar
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    return state_table[ cur_state ]( data );
};

int main( void ) {
    state_t cur_state = STATE_INITIAL;
    instance_data_t data;

    while ( 1 ) {
        cur_state = run_state( cur_state, &data );

        // do other program logic, run other state machines, etc
    }
}

当然,这可以扩展为支持多个状态机等。还可以适应转换动作:

typedef void transition_func_t( instance_data_t *data );

void do_initial_to_foo( instance_data_t *data );
void do_foo_to_bar( instance_data_t *data );
void do_bar_to_initial( instance_data_t *data );
void do_bar_to_foo( instance_data_t *data );
void do_bar_to_bar( instance_data_t *data );

transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = {
    { NULL,              do_initial_to_foo, NULL },
    { NULL,              NULL,              do_foo_to_bar },
    { do_bar_to_initial, do_bar_to_foo,     do_bar_to_bar }
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    state_t new_state = state_table[ cur_state ]( data );
    transition_func_t *transition =
               transition_table[ cur_state ][ new_state ];

    if ( transition ) {
        transition( data );
    }

    return new_state;
};

表驱动的方法更易于维护和扩展,更易于映射到状态图。

我们可能已经看到我对另一个提到FSM的C问题的回答!这是我的方法:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

定义以下宏

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

可以对其进行修改以适合特定情况。例如,我们可能具有要驱动FSM的文件" FSMFILE",因此我们可以将读取下一个字符的操作合并到宏本身中:

#define FSM
#define STATE(x)         s_##x : FSMCHR = fgetc(FSMFILE); sn_##x :
#define NEXTSTATE(x)     goto s_##x
#define NEXTSTATE_NR(x)  goto sn_##x

现在,我们有两种类型的过渡:一种进入状态并读取新字符,另一种则不消耗任何输入就进入状态。

我们还可以使用以下方式自动处理EOF:

#define STATE(x)  s_##x  : if ((FSMCHR = fgetc(FSMFILE) == EOF)\
                             goto sx_endfsm;\
                  sn_##x :

#define ENDFSM    sx_endfsm:

这种方法的好处是,我们可以将绘制的状态图直接转换为工作代码,相反,可以轻松地从代码中绘制状态图。

在其他用于实现FSM的技术中,转换的结构被埋在控制结构中(同时,如果...则是……),并由变量值(通常是"状态"变量)控制,而关联好变量可能是一项复杂的任务绘制成复杂的代码。

我从一篇伟大的"计算机语言"杂志上发表的一篇文章中学到了这种技术,不幸的是,该杂志不再出版。

对于简单的状态机,只需为状态使用switch语句和枚举类型。根据输入在switch语句内进行转换。在实际程序中,我们显然会更改" if(input)"以检查过渡点。希望这可以帮助。

typedef enum
{
    STATE_1 = 0,
    STATE_2,
    STATE_3
} my_state_t;

my_state_t state = STATE_1;

void foo(char input)
{
    ...
    switch(state)
    {
        case STATE_1:
            if(input)
                state = STATE_2;
            break;
        case STATE_2:
            if(input)
                state = STATE_3;
            else
                state = STATE_1;
            break;
        case STATE_3:
            ...
            break;
    }
    ...
}

switch()是在C中实现状态机的一种强大且标准的方法,但是如果我们有很多状态,它会降低可维护性。另一种常见的方法是使用函数指针来存储下一个状态。这个简单的示例实现了置位/复位触发器:

/* Implement each state as a function with the same prototype */
void state_one(int set, int reset);
void state_two(int set, int reset);

/* Store a pointer to the next state */
void (*next_state)(int set, int reset) = state_one;

/* Users should call next_state(set, reset). This could
   also be wrapped by a real function that validated input
   and dealt with output rather than calling the function
   pointer directly. */

/* State one transitions to state one if set is true */
void state_one(int set, int reset) {
    if(set)
        next_state = state_two;
}

/* State two transitions to state one if reset is true */
void state_two(int set, int reset) {
    if(reset)
        next_state = state_one;
}

我们可能需要研究libero FSM生成器软件。通过状态描述语言和/或者(Windows)状态图编辑器,我们可以生成C,C ++,java和许多其他代码,以及精美的文档和图。
来自iMatix的源代码和二进制文件

问题类似于"是否存在典型的数据库实施模式"?
答案取决于我们想要实现什么?如果要实现更大的确定性状态机,则可以使用模型和状态机生成器。
可以在www.StateSoft.org SM Gallery上查看示例。贾努斯·多布罗沃尔斯基(Janusz Dobrowolski)