Eines meiner Lieblingsmuster ist das Zustandsmuster. Reagieren Sie auf dieselben Eingaben oder verhalten Sie sich anders.
Eines der Probleme bei der Verwendung von switch / case-Anweisungen für Zustandsautomaten besteht darin, dass das Erstellen / Verwalten von switch / case-Dateien schwieriger / unhandlicher wird, unorganisierten Spaghetti-Code fördert und zunehmend schwieriger zu ändern ist, ohne etwas zu beschädigen. Ich finde, dass die Verwendung von Entwurfsmustern mir hilft, meine Daten besser zu organisieren, was den ganzen Punkt der Abstraktion ausmacht. Anstatt Ihren Statuscode anhand des Status zu entwerfen, aus dem Sie gekommen sind, strukturieren Sie Ihren Code so, dass er den Status aufzeichnet, wenn Sie einen neuen Status eingeben. Auf diese Weise erhalten Sie effektiv eine Aufzeichnung Ihres vorherigen Zustands. Ich mag die Antwort von @ JoshPetit und habe seine Lösung einen Schritt weiter gebracht, direkt aus dem GoF-Buch:
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;
}
Für die meisten Staatsmaschinen, insb. Bei endlichen Zustandsmaschinen weiß jeder Zustand, wie sein nächster Zustand aussehen soll und welche Kriterien für den Übergang in seinen nächsten Zustand gelten. Bei Designs mit losen Zuständen ist dies möglicherweise nicht der Fall, daher besteht die Möglichkeit, die API für Übergangszustände verfügbar zu machen. Wenn Sie mehr Abstraktion wünschen, kann jeder Statushandler in eine eigene Datei aufgeteilt werden, die den konkreten Statushandlern im GoF-Buch entspricht. Wenn Ihr Entwurf mit nur wenigen Status einfach ist, können sowohl stateCtxt.c als auch statehandlers.c der Einfachheit halber in einer einzigen Datei kombiniert werden.