Один из моих любимых паттернов - паттерн государственного проектирования. Реагировать или вести себя по-разному на один и тот же заданный набор входных данных.
Одна из проблем с использованием операторов switch / case для конечных автоматов заключается в том, что по мере того, как вы создаете больше состояний, switch / case становится все труднее / громоздко для чтения / обслуживания, способствует неорганизованному спагетти-коду и все труднее изменить, не сломав что-то. Я считаю, что использование шаблонов проектирования помогает мне лучше организовать мои данные, и в этом весь смысл абстракции. Вместо того, чтобы разрабатывать код состояния на основе того, из какого состояния вы пришли, вместо этого структурируйте свой код так, чтобы он записывал состояние, когда вы входите в новое состояние. Таким образом, вы фактически получите запись своего предыдущего состояния. Мне нравится ответ @JoshPetit, и я продвинул его решение еще на один шаг, взятый прямо из книги GoF:
stateCtxt.h:
#define STATE (void *)
typedef enum fsmSignal
{
eEnter =0,
eNormal,
eExit
}FsmSignalT;
typedef struct fsm
{
FsmSignalT signal;
// StateT is an enum that you can define any which way you want
StateT currentState;
}FsmT;
extern int STATECTXT_Init(void);
/* optionally allow client context to set the target state */
extern STATECTXT_Set(StateT stateID);
extern void STATECTXT_Handle(void *pvEvent);
stateCtxt.c:
#include "stateCtxt.h"
#include "statehandlers.h"
typedef STATE (*pfnStateT)(FsmSignalT signal, void *pvEvent);
static FsmT fsm;
static pfnStateT UsbState ;
int STATECTXT_Init(void)
{
UsbState = State1;
fsm.signal = eEnter;
// use an enum for better maintainability
fsm.currentState = '1';
(*UsbState)( &fsm, pvEvent);
return 0;
}
static void ChangeState( FsmT *pFsm, pfnStateT targetState )
{
// Check to see if the state has changed
if (targetState != NULL)
{
// Call current state's exit event
pFsm->signal = eExit;
STATE dummyState = (*UsbState)( pFsm, pvEvent);
// Update the State Machine structure
UsbState = targetState ;
// Call the new state's enter event
pFsm->signal = eEnter;
dummyState = (*UsbState)( pFsm, pvEvent);
}
}
void STATECTXT_Handle(void *pvEvent)
{
pfnStateT newState;
if (UsbState != NULL)
{
fsm.signal = eNormal;
newState = (*UsbState)( &fsm, pvEvent );
ChangeState( &fsm, newState );
}
}
void STATECTXT_Set(StateT stateID)
{
prevState = UsbState;
switch (stateID)
{
case '1':
ChangeState( State1 );
break;
case '2':
ChangeState( State2);
break;
case '3':
ChangeState( State3);
break;
}
}
statehandlers.h:
/* define state handlers */
extern STATE State1(void);
extern STATE State2(void);
extern STATE State3(void);
statehandlers.c:
#include "stateCtxt.h:"
/* Define behaviour to given set of inputs */
STATE State1(FsmT *fsm, void *pvEvent)
{
STATE nextState;
/* do some state specific behaviours
* here
*/
/* fsm->currentState currently contains the previous state
* just before it gets updated, so you can implement behaviours
* which depend on previous state here
*/
fsm->currentState = '1';
/* Now, specify the next state
* to transition to, or return null if you're still waiting for
* more stuff to process.
*/
switch (fsm->signal)
{
case eEnter:
nextState = State2;
break;
case eNormal:
nextState = null;
break;
case eExit:
nextState = State2;
break;
}
return nextState;
}
STATE State3(FsmT *fsm, void *pvEvent)
{
/* do some state specific behaviours
* here
*/
fsm->currentState = '2';
/* Now, specify the next state
* to transition to
*/
return State1;
}
STATE State2(FsmT *fsm, void *pvEvent)
{
/* do some state specific behaviours
* here
*/
fsm->currentState = '3';
/* Now, specify the next state
* to transition to
*/
return State3;
}
Для большинства государственных машин, особенно. Конечные автоматы, каждое состояние будет знать, каким должно быть его следующее состояние, а также критерии перехода к следующему состоянию. Для проектов со свободным состоянием это может быть не так, поэтому есть возможность предоставить API для перехода между состояниями. Если вам нужно больше абстракции, каждый обработчик состояния может быть выделен в отдельный файл, который эквивалентен конкретным обработчикам состояния в книге GoF. Если ваш дизайн прост и содержит всего несколько состояний, то для простоты и stateCtxt.c, и statehandlers.c можно объединить в один файл.