내가 가장 좋아하는 패턴 중 하나는 상태 디자인 패턴입니다. 동일한 입력 세트에 대해 다르게 반응하거나 행동합니다.
상태 머신에 대해 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;
}
대부분의 상태 머신의 경우 esp. 유한 상태 머신, 각 상태는 다음 상태가 무엇인지, 다음 상태로 전환하기위한 기준을 알고 있습니다. 느슨한 상태 설계의 경우에는 그렇지 않을 수 있으므로 상태 전환을 위해 API를 노출하는 옵션이 있습니다. 더 많은 추상화를 원하면 각 상태 핸들러를 자체 파일로 분리 할 수 있습니다. 이는 GoF 책의 구체적인 상태 핸들러와 동일합니다. 디자인이 몇 개의 상태만으로 간단하다면 stateCtxt.c와 statehandlers.c는 단순성을 위해 단일 파일로 결합 될 수 있습니다.