Jednym z moich ulubionych wzorów jest wzór projektowania państwowego. Reaguj lub zachowuj się inaczej w przypadku tego samego zestawu danych wejściowych.
Jednym z problemów związanych z używaniem instrukcji switch / case dla maszyn stanu jest to, że gdy tworzysz więcej stanów, przełącznik / case staje się trudniejszy / nieporęczny do odczytania / utrzymania, promuje niezorganizowany kod spaghetti i coraz trudniej jest go zmienić bez zepsucia czegoś. Uważam, że używanie wzorców projektowych pomaga mi lepiej organizować dane, co jest celem abstrakcji. Zamiast projektować kod stanu w oparciu o stan, z którego pochodzisz, skonstruuj kod tak, aby rejestrował stan, gdy wchodzisz w nowy stan. W ten sposób skutecznie uzyskasz zapis swojego poprzedniego stanu. Podoba mi się odpowiedź @ JoshPetita i poszedłem o krok dalej, zaczerpnięte prosto z książki 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;
}
W przypadku większości maszyn stanowych, zwł. Maszyny skończone, każdy stan będzie wiedział, jaki powinien być jego następny stan i jakie są kryteria przejścia do następnego stanu. W przypadku projektów stanu swobodnego może tak nie być, stąd opcja udostępnienia API dla stanów przejściowych. Jeśli chcesz więcej abstrakcji, każdy program obsługi stanu może zostać oddzielony do własnego pliku, który jest równoważny z konkretnymi procedurami obsługi stanu w książce GoF. Jeśli twój projekt jest prosty i ma tylko kilka stanów, wówczas zarówno stateCtxt.c, jak i statehandlers.c mogą zostać połączone w jeden plik dla uproszczenia.