รูปแบบหนึ่งที่ฉันชอบคือรูปแบบการออกแบบของรัฐ ตอบสนองหรือทำงานแตกต่างจากอินพุตชุดเดียวกัน
ปัญหาอย่างหนึ่งในการใช้คำสั่ง switch / case สำหรับเครื่อง state คือเมื่อคุณสร้างสถานะมากขึ้นสวิตช์ / case จะยากขึ้น / ไม่สะดวกในการอ่าน / บำรุงรักษาส่งเสริมรหัส spaghetti ที่ไม่มีการรวบรวมและเปลี่ยนแปลงได้ยากขึ้นโดยไม่ทำลายบางสิ่ง ฉันพบว่าการใช้รูปแบบการออกแบบช่วยให้ฉันจัดระเบียบข้อมูลได้ดีขึ้นซึ่งเป็นจุดรวมของนามธรรม แทนที่จะออกแบบรหัสสถานะของคุณตามสถานะที่คุณมาให้จัดโครงสร้างรหัสของคุณแทนเพื่อให้บันทึกสถานะเมื่อคุณเข้าสู่สถานะใหม่ ด้วยวิธีนี้คุณจะได้รับบันทึกสถานะก่อนหน้านี้อย่างมีประสิทธิภาพ ฉันชอบคำตอบของ @ 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;
}
สำหรับเครื่อง State ส่วนใหญ่โดยเฉพาะ Finite State Machines แต่ละรัฐจะรู้ว่าสถานะต่อไปควรเป็นอย่างไรและเกณฑ์สำหรับการเปลี่ยนไปสู่สถานะถัดไป สำหรับการออกแบบสถานะแบบหลวมอาจไม่เป็นเช่นนั้นดังนั้นตัวเลือกในการเปิดเผย API สำหรับสถานะการเปลี่ยน หากคุณต้องการความเป็นนามธรรมมากขึ้นตัวจัดการแต่ละสถานะสามารถแยกออกเป็นไฟล์ของตัวเองได้ซึ่งเทียบเท่ากับตัวจัดการสถานะที่เป็นรูปธรรมในหนังสือ GoF หากการออกแบบของคุณเรียบง่ายโดยมีสถานะเพียงไม่กี่สถานะทั้ง stateCtxt.c และ statehandlers.c สามารถรวมเป็นไฟล์เดียวเพื่อความเรียบง่าย