Один з моїх улюблених моделей - модель дизайну штату. Відповідайте або поводьтесь інакше на той самий заданий набір входів.
Однією з проблем використання операторів переключення / справи для державних машин є те, що, коли ви створюєте більше станів, перемикач / випадки стає важче / важко читати / підтримувати, сприяє неорганізованому коду спагетті і все складніше змінювати, не змінюючи щось. Я вважаю, що використання моделей дизайну допомагає мені краще впорядкувати свої дані, в чому вся суть абстрагування. Замість того, щоб розробити код свого стану, з якого стану ви прийшли, замість цього структуруйте свій код так, щоб він записував стан, коли ви входите в новий стан. Таким чином, ви ефективно отримуєте запис свого попереднього стану. Мені подобається відповідь @ 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 можуть бути об'єднані в один файл для простоти.