私のお気に入りのパターンの1つは、ステートデザインパターンです。与えられた同じ入力セットに対して異なる応答または動作をします。
ステートマシンにswitch / caseステートメントを使用する際の問題の1つは、ステートを作成するにつれて、スイッチ/ケースの読み取り/維持が困難/扱いにくくなり、組織化されていないスパゲッティコードを促進し、何かを壊さずに変更することがますます困難になることです。デザインパターンを使用すると、データをより適切に整理するのに役立ちます。これが抽象化の全体のポイントです。元の状態に基づいて状態コードを設計する代わりに、コードを構造化して、新しい状態に入ったときに状態を記録するようにします。これにより、以前の状態の記録を効果的に取得できます。私は@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の両方を1つのファイルにまとめて簡単にすることができます。