มีรูปแบบการใช้งานเครื่องของรัฐโดยทั่วไปหรือไม่?


118

เราจำเป็นต้องใช้เครื่องของรัฐที่เรียบง่ายในC
คำสั่งสวิตช์มาตรฐานเป็นวิธีที่ดีที่สุดหรือไม่?
เรามีสถานะปัจจุบัน (สถานะ) และตัวกระตุ้นสำหรับการเปลี่ยนแปลง


switch(state)
{
  case STATE_1:
     state = DoState1(transition);
     break;
  case STATE_2:
     state = DoState2(transition);
     break;
}
...
DoState2(int transition)
{
   // Do State Work
   ...
   if(transition == FROM_STATE_2) {
     // New state when doing STATE 2 -> STATE 2
   }
   if(transition == FROM_STATE_1) {
    // New State when moving STATE 1 -> STATE 2
   }
   return new_state;
}

มีวิธีที่ดีกว่าสำหรับเครื่องสเตทง่ายๆ

แก้ไข: สำหรับ C ++ ฉันคิดว่าไลบรารีBoost Statechartอาจเป็นวิธีที่จะไป อย่างไรก็ตามมันไม่ได้ช่วยอะไรกับ C. ให้มุ่งเน้นไปที่กรณีการใช้งาน C


คำตอบ:


134

ฉันชอบใช้วิธีการขับเคลื่อนด้วยตารางสำหรับเครื่องของรัฐส่วนใหญ่:

typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t;
typedef struct instance_data instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_initial( instance_data_t *data );
state_t do_state_foo( instance_data_t *data );
state_t do_state_bar( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
    do_state_initial, do_state_foo, do_state_bar
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    return state_table[ cur_state ]( data );
};

int main( void ) {
    state_t cur_state = STATE_INITIAL;
    instance_data_t data;

    while ( 1 ) {
        cur_state = run_state( cur_state, &data );

        // do other program logic, run other state machines, etc
    }
}

แน่นอนว่าสิ่งนี้สามารถขยายได้เพื่อรองรับเครื่องหลายสถานะเป็นต้นการดำเนินการเปลี่ยนสามารถรองรับได้เช่นกัน:

typedef void transition_func_t( instance_data_t *data );

void do_initial_to_foo( instance_data_t *data );
void do_foo_to_bar( instance_data_t *data );
void do_bar_to_initial( instance_data_t *data );
void do_bar_to_foo( instance_data_t *data );
void do_bar_to_bar( instance_data_t *data );

transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = {
    { NULL,              do_initial_to_foo, NULL },
    { NULL,              NULL,              do_foo_to_bar },
    { do_bar_to_initial, do_bar_to_foo,     do_bar_to_bar }
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    state_t new_state = state_table[ cur_state ]( data );
    transition_func_t *transition =
               transition_table[ cur_state ][ new_state ];

    if ( transition ) {
        transition( data );
    }

    return new_state;
};

แนวทางที่ขับเคลื่อนด้วยตารางนั้นง่ายต่อการบำรุงรักษาและขยายและง่ายกว่าในการแมปกับไดอะแกรมสถานะ


วิธีที่ดีมากในการเริ่มต้นอย่างน้อยก็เป็นจุดเริ่มต้นสำหรับฉัน ข้อสังเกตหนึ่งบรรทัดแรกของ run_state () มีความซน "." ที่ไม่ควรมี
Atilla Filiz

2
จะดีกว่าถ้าคำตอบนี้อย่างน้อยควรพูด 2 คำเกี่ยวกับอีกสองวิธี: วิธี "ทั่วโลก" ที่มีสวิตช์เคสขนาดใหญ่และแยกรัฐด้วยรูปแบบการออกแบบของรัฐและปล่อยให้แต่ละรัฐจัดการการเปลี่ยนผ่านของมันเอง
erikbwork

สวัสดีฉันรู้ว่าโพสต์นี้เก่า แต่ฉันหวังว่าฉันจะได้รับคำตอบ :) อะไรที่แน่นอนในตัวแปร instance_data_t? ฉันสงสัยว่าจะเปลี่ยนสถานะในอินเทอร์รัปต์ได้อย่างไร ... เป็นวิธีที่ดีในการจัดเก็บข้อมูลเกี่ยวกับการขัดจังหวะที่ประมวลผลในตัวแปรนี้หรือไม่ ตัวอย่างเช่นข้อมูลร้านค้าที่กดปุ่มดังนั้นควรเปลี่ยนสถานะ
grongor

@GRoNGoR ฟังฉันเหมือนคุณกำลังจัดการกับเครื่องรัฐที่ขับเคลื่อนด้วยเหตุการณ์ ฉันคิดว่าคุณสามารถใช้เพื่อเก็บข้อมูลเหตุการณ์ได้อย่างแน่นอน
Zimano

3
สัมผัสได้ดีจริงๆว่ามีการกำหนด NUM_STATES อย่างไร
Albin Stigo

25

คุณอาจได้เห็นคำตอบของฉันสำหรับคำถาม C อื่นที่ฉันพูดถึง FSM! นี่คือวิธีการ:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

ด้วยมาโครต่อไปนี้ที่กำหนดไว้

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

สิ่งนี้สามารถแก้ไขได้เพื่อให้เหมาะกับกรณีเฉพาะ ตัวอย่างเช่นคุณอาจมีไฟล์FSMFILEที่คุณต้องการขับเคลื่อน FSM ของคุณดังนั้นคุณสามารถรวมการกระทำของการอ่านอักขระถัดไปลงในมาโครได้:

#define FSM
#define STATE(x)         s_##x : FSMCHR = fgetc(FSMFILE); sn_##x :
#define NEXTSTATE(x)     goto s_##x
#define NEXTSTATE_NR(x)  goto sn_##x

ตอนนี้คุณมีการเปลี่ยนสองประเภท: หนึ่งไปที่สถานะและอ่านอักขระใหม่อีกตัวหนึ่งไปที่สถานะโดยไม่ต้องใช้อินพุตใด ๆ

คุณยังสามารถจัดการ EOF โดยอัตโนมัติได้ด้วยสิ่งต่อไปนี้

#define STATE(x)  s_##x  : if ((FSMCHR = fgetc(FSMFILE) == EOF)\
                             goto sx_endfsm;\
                  sn_##x :

#define ENDFSM    sx_endfsm:

สิ่งที่ดีของแนวทางนี้คือคุณสามารถแปลแผนภาพสถานะที่คุณวาดเป็นรหัสการทำงานได้โดยตรงและในทางกลับกันคุณสามารถวาดแผนภาพสถานะจากรหัสได้อย่างง่ายดาย

ในเทคนิคอื่น ๆ สำหรับการนำ FSM ไปใช้โครงสร้างของการเปลี่ยนจะถูกฝังไว้ในโครงสร้างควบคุม (ในขณะที่ถ้าสวิตช์ ... ) และควบคุมโดยค่าตัวแปร ( stateตัวแปรปลาย) และอาจเป็นงานที่ซับซ้อนในการเชื่อมโยงแผนผังที่ดีกับ a รหัสที่ซับซ้อน

ฉันได้เรียนรู้เทคนิคนี้จากบทความที่ปรากฏในนิตยสาร "ภาษาคอมพิวเตอร์" ซึ่งน่าเสียดายที่ไม่มีการตีพิมพ์อีกต่อไป


1
โดยพื้นฐานแล้ว FSM ที่ดีเป็นข้อมูลเกี่ยวกับความสามารถในการอ่าน สิ่งนี้ให้อินเทอร์เฟซที่ดีและการใช้งานนั้นดีเท่าที่จะได้รับ เป็นเรื่องที่น่าเสียดายที่ไม่มีโครงสร้าง FSM ดั้งเดิมในภาษา ฉันเห็นมันเป็นส่วนเสริมของ C1X ในตอนนี้!
Kelden Cowan

3
ฉันชอบแนวทางนี้สำหรับแอปพลิเคชันแบบฝัง มีวิธีใช้แนวทางนี้กับเครื่องรัฐที่ขับเคลื่อนด้วยเหตุการณ์หรือไม่?
ARF

13

ฉันยังใช้วิธีตาราง อย่างไรก็ตามมีค่าใช้จ่าย ทำไมต้องเก็บรายการตัวชี้ที่สอง ฟังก์ชันใน C ที่ไม่มี () เป็นตัวชี้ const ดังนั้นคุณสามารถทำได้:

struct state;
typedef void (*state_func_t)( struct state* );

typedef struct state
{
  state_func_t function;

  // other stateful data

} state_t;

void do_state_initial( state_t* );
void do_state_foo( state_t* );
void do_state_bar( state_t* );

void run_state( state_t* i ) {
    i->function(i);
};

int main( void ) {
    state_t state = { do_state_initial };

    while ( 1 ) {
        run_state( state );

        // do other program logic, run other state machines, etc
    }
}

แน่นอนขึ้นอยู่กับปัจจัยความกลัวของคุณ (เช่นความปลอดภัยและความเร็ว) คุณอาจต้องการตรวจสอบตัวชี้ที่ถูกต้อง สำหรับเครื่องของรัฐที่มีขนาดใหญ่กว่าสามสถานะขึ้นไปแนวทางข้างต้นควรมีคำแนะนำน้อยกว่าสวิตช์ที่เทียบเท่าหรือแนวทางตาราง คุณสามารถปรับขนาดมาโครเป็น:

#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))

นอกจากนี้ฉันรู้สึกได้จากตัวอย่างของ OP ว่ามีความเรียบง่ายที่ควรทำเมื่อคิดเกี่ยวกับ / ออกแบบเครื่องของรัฐ ฉันไม่คิดว่าควรใช้สถานะการเปลี่ยนแปลงเพื่อตรรกะ แต่ละฟังก์ชันของรัฐควรสามารถดำเนินการตามบทบาทที่กำหนดได้โดยไม่ต้องมีความรู้อย่างชัดเจนเกี่ยวกับสถานะในอดีต โดยทั่วไปคุณจะออกแบบวิธีการเปลี่ยนจากสถานะที่คุณอยู่ในสถานะอื่น

สุดท้ายอย่าเริ่มการออกแบบเครื่องของรัฐตามขอบเขต "ฟังก์ชัน" ใช้ฟังก์ชันย่อยสำหรับสิ่งนั้น แทนที่จะแบ่งรัฐตามเวลาที่คุณจะต้องรอให้มีอะไรเกิดขึ้นก่อนจึงจะดำเนินการต่อได้ วิธีนี้จะช่วยลดจำนวนครั้งที่คุณต้องเรียกใช้เครื่องสถานะก่อนที่จะได้รับผลลัพธ์ สิ่งนี้อาจมีความสำคัญเมื่อเขียนฟังก์ชัน I / O หรือตัวจัดการขัดจังหวะ

นอกจากนี้ข้อดีข้อเสียบางประการของคำสั่งสวิตช์แบบคลาสสิก:

ข้อดี:

  • เป็นภาษาจึงมีการจัดทำเอกสารและชัดเจน
  • มีการกำหนดรัฐที่เรียกว่า
  • สามารถดำเนินการหลายสถานะในการเรียกใช้ฟังก์ชันเดียว
  • รหัสทั่วไปสำหรับทุกสถานะสามารถดำเนินการได้ก่อนและหลังคำสั่งสวิตช์

จุดด้อย:

  • สามารถดำเนินการหลายสถานะในการเรียกใช้ฟังก์ชันเดียว
  • รหัสทั่วไปสำหรับทุกสถานะสามารถดำเนินการได้ก่อนและหลังคำสั่งสวิตช์
  • การใช้งานสวิตช์อาจช้า

สังเกตสองแอตทริบิวต์ที่มีทั้ง pro และ con ฉันคิดว่าสวิตช์เปิดโอกาสให้มีการแบ่งปันระหว่างรัฐมากเกินไปและการพึ่งพาซึ่งกันและกันระหว่างรัฐอาจไม่สามารถจัดการได้ อย่างไรก็ตามสำหรับรัฐจำนวนน้อยอาจเป็นรัฐที่อ่านง่ายและบำรุงรักษาได้มากที่สุด


10

สำหรับเครื่องสถานะธรรมดาเพียงแค่ใช้คำสั่งสวิตช์และประเภท enum สำหรับสถานะของคุณ ทำการเปลี่ยนภายในคำสั่งสวิตช์ตามข้อมูลที่คุณป้อน ในโปรแกรมจริงคุณจะต้องเปลี่ยน "if (อินพุต)" อย่างชัดเจนเพื่อตรวจสอบจุดเปลี่ยนของคุณ หวังว่านี่จะช่วยได้

typedef enum
{
    STATE_1 = 0,
    STATE_2,
    STATE_3
} my_state_t;

my_state_t state = STATE_1;

void foo(char input)
{
    ...
    switch(state)
    {
        case STATE_1:
            if(input)
                state = STATE_2;
            break;
        case STATE_2:
            if(input)
                state = STATE_3;
            else
                state = STATE_1;
            break;
        case STATE_3:
            ...
            break;
    }
    ...
}

1
อาจคุ้มค่าที่จะใส่ "สถานะ" ไว้ในฟังก์ชันและทำให้เป็นแบบคงที่
Steve Melnikoff

2
@Steve Melnikoff: เฉพาะในกรณีที่คุณมีเครื่องสถานะเพียงเครื่องเดียว เก็บไว้นอกฟังก์ชันและคุณสามารถมีอาร์เรย์ของเครื่องสถานะที่มีสถานะของตนเองได้
Vicky

@Vicky: ฟังก์ชันหนึ่งสามารถมีเครื่องสถานะได้มากเท่าที่คุณต้องการโดยมีอาร์เรย์ของตัวแปรสถานะหากต้องการซึ่งสามารถอยู่ภายในฟังก์ชัน (เป็นตัวแปรคงที่) หากไม่ได้ใช้ที่อื่น
Steve Melnikoff

10

ในUML Distilled ของ Martin Fowlerเขากล่าว (ไม่ได้ตั้งใจเล่นสำนวน) ในบทที่ 10 State Machine Diagrams (เน้นของฉัน):

แผนภาพรัฐสามารถดำเนินการในสามวิธีหลัก: สวิทช์ที่ซ้อนกันในรูปแบบของรัฐและ ตารางรัฐ

ลองใช้ตัวอย่างที่เรียบง่ายของสถานะการแสดงผลของโทรศัพท์มือถือ:

ใส่คำอธิบายภาพที่นี่

สวิตช์ที่ซ้อนกัน

Fowler ยกตัวอย่างรหัส C # แต่ฉันได้ปรับให้เข้ากับตัวอย่างของฉันแล้ว

public void HandleEvent(PhoneEvent anEvent) {
    switch (CurrentState) {
    case PhoneState.ScreenOff:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            if (powerLow) { // guard condition
                DisplayLowPowerMessage(); // action
                // CurrentState = PhoneState.ScreenOff;
            } else {
                CurrentState = PhoneState.ScreenOn;
            }
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenOn:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            CurrentState = PhoneState.ScreenOff;
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenCharging:
        switch (anEvent) {
        case PhoneEvent.UnplugPower:
            CurrentState = PhoneState.ScreenOff;
            break;
        }
        break;
    }
}

รูปแบบของรัฐ

นี่คือการนำตัวอย่างของฉันไปใช้กับรูปแบบ GoF State:

ใส่คำอธิบายภาพที่นี่

ตารางสถานะ

รับแรงบันดาลใจจาก Fowler นี่คือตารางสำหรับตัวอย่างของฉัน:

การดำเนินการป้องกันเหตุการณ์ของรัฐเป้าหมายสถานะต้นทาง
-------------------------------------------------- ------------------------------------
ScreenOff ScreenOff pressButton powerLow displayLowPowerMessage  
ScreenOff ScreenOn กดปุ่ม! powerLow
ScreenOn ScreenOff กดปุ่ม
ScreenOff Screen ปลั๊กชาร์จพลังงาน
ScreenOn ScreenCharging plugPower
ScreenCharging Screen ปิดการถอดปลั๊ก Power

การเปรียบเทียบ

สวิตช์ที่ซ้อนกันจะเก็บตรรกะทั้งหมดไว้ในจุดเดียว แต่รหัสอาจอ่านได้ยากเมื่อมีสถานะและช่วงการเปลี่ยนภาพจำนวนมาก อาจปลอดภัยและตรวจสอบได้ง่ายกว่าวิธีอื่น ๆ (ไม่มีความหลากหลายหรือการตีความ)

การนำรูปแบบสถานะไปใช้อาจกระจายตรรกะไปยังคลาสต่างๆที่แยกจากกันซึ่งอาจทำให้ความเข้าใจในภาพรวมเป็นปัญหาได้ ในทางกลับกันชั้นเรียนขนาดเล็กจะเข้าใจง่ายแยกกัน การออกแบบมีความเปราะบางเป็นพิเศษหากคุณเปลี่ยนพฤติกรรมโดยการเพิ่มหรือลบการเปลี่ยนเนื่องจากเป็นวิธีการในลำดับชั้นและอาจมีการเปลี่ยนแปลงโค้ดมากมาย หากคุณดำเนินชีวิตตามหลักการออกแบบของอินเทอร์เฟซขนาดเล็กคุณจะเห็นว่ารูปแบบนี้ทำได้ไม่ดีนัก อย่างไรก็ตามหากเครื่องสถานะมีความเสถียรก็ไม่จำเป็นต้องมีการเปลี่ยนแปลงดังกล่าว

แนวทางตารางสถานะต้องการการเขียนล่ามบางประเภทสำหรับเนื้อหา (อาจง่ายกว่าถ้าคุณมีการไตร่ตรองในภาษาที่คุณใช้) ซึ่งอาจเป็นงานที่ต้องทำมากมาย ดังที่ Fowler ชี้ให้เห็นหากตารางของคุณแยกจากรหัสของคุณคุณสามารถปรับเปลี่ยนลักษณะการทำงานของซอฟต์แวร์ได้โดยไม่ต้องคอมไพล์ใหม่ อย่างไรก็ตามสิ่งนี้มีผลกระทบด้านความปลอดภัยบางประการ ซอฟต์แวร์ทำงานตามเนื้อหาของไฟล์ภายนอก

แก้ไข (ไม่ใช่ภาษา C จริงๆ)

มีอินเทอร์เฟซที่คล่องแคล่ว (หรือที่เรียกว่าภาษาเฉพาะโดเมนภายใน) ด้วยเช่นกันซึ่งอาจได้รับการอำนวยความสะดวกโดยภาษาที่มีฟังก์ชันชั้นหนึ่ง มีไลบรารี Statelessและบล็อกนั้นแสดงตัวอย่างง่ายๆพร้อมรหัส Java การดำเนินงาน (ก่อน Java8)จะกล่าวถึง ฉันได้แสดงตัวอย่าง Python บน GitHubเช่นกัน


คุณใช้ซอฟต์แวร์อะไรในการสร้างภาพ
sjas

1
ฉันสงสัยว่ามันอาจถูกสร้างขึ้นผ่าน PlantUML plantuml.com/state-diagram
Seidleroni


4

สำหรับกรณีง่ายๆคุณสามารถเปลี่ยนวิธีการสไตล์ของคุณได้ สิ่งที่ฉันพบว่าทำงานได้ดีในอดีตคือการจัดการกับการเปลี่ยนแปลงด้วย:

static int current_state;    // should always hold current state -- and probably be an enum or something

void state_leave(int new_state) {
    // do processing on what it means to enter the new state
    // which might be dependent on the current state
}

void state_enter(int new_state) {
    // do processing on what is means to leave the current atate
    // might be dependent on the new state

    current_state = new_state;
}

void state_process() {
    // switch statement to handle current state
}

ฉันไม่รู้อะไรเลยเกี่ยวกับไลบรารีบูสต์ แต่วิธีการประเภทนี้ง่ายมากไม่ต้องการการอ้างอิงจากภายนอกและง่ายต่อการใช้งาน


4

สวิตช์ () เป็นวิธีที่มีประสิทธิภาพและเป็นมาตรฐานในการนำเครื่องสเตตมาใช้ใน C แต่สามารถลดความสามารถในการบำรุงรักษาลงได้หากคุณมีสถานะจำนวนมาก อีกวิธีหนึ่งคือการใช้ตัวชี้ฟังก์ชันเพื่อเก็บสถานะถัดไป ตัวอย่างง่ายๆนี้ใช้ชุด / รีเซ็ตฟลิปฟล็อป:

/* Implement each state as a function with the same prototype */
void state_one(int set, int reset);
void state_two(int set, int reset);

/* Store a pointer to the next state */
void (*next_state)(int set, int reset) = state_one;

/* Users should call next_state(set, reset). This could
   also be wrapped by a real function that validated input
   and dealt with output rather than calling the function
   pointer directly. */

/* State one transitions to state one if set is true */
void state_one(int set, int reset) {
    if(set)
        next_state = state_two;
}

/* State two transitions to state one if reset is true */
void state_two(int set, int reset) {
    if(reset)
        next_state = state_one;
}

4

ฉันพบการใช้งาน C ที่ลื่นไหลอย่างแท้จริงของ Moore FSM ในหลักสูตร edx.org Embedded Systems - Shape the World UTAustinX - UT.6.02x ตอนที่ 10 โดย Jonathan Valvano และ Ramesh Yerraballi ....

struct State {
  unsigned long Out;  // 6-bit pattern to output
  unsigned long Time; // delay in 10ms units 
  unsigned long Next[4]; // next state for inputs 0,1,2,3
}; 

typedef const struct State STyp;

//this example has 4 states, defining constants/symbols using #define
#define goN   0
#define waitN 1
#define goE   2
#define waitE 3


//this is the full FSM logic coded into one large array of output values, delays, 
//and next states (indexed by values of the inputs)
STyp FSM[4]={
 {0x21,3000,{goN,waitN,goN,waitN}}, 
 {0x22, 500,{goE,goE,goE,goE}},
 {0x0C,3000,{goE,goE,waitE,waitE}},
 {0x14, 500,{goN,goN,goN,goN}}};
unsigned long currentState;  // index to the current state 

//super simple controller follows
int main(void){ volatile unsigned long delay;
//embedded micro-controller configuration omitteed [...]
  currentState = goN;  
  while(1){
    LIGHTS = FSM[currentState].Out;  // set outputs lines (from FSM table)
    SysTick_Wait10ms(FSM[currentState].Time);
    currentState = FSM[currentState].Next[INPUT_SENSORS];  
  }
}

2

คุณอาจต้องการตรวจสอบซอฟต์แวร์ตัวสร้างlibero FSM จากภาษาคำอธิบายสถานะและ / หรือโปรแกรมแก้ไขไดอะแกรมสถานะ (windows) คุณสามารถสร้างโค้ดสำหรับ C, C ++, java และอื่น ๆ อีกมากมาย ... รวมถึงเอกสารและไดอะแกรมที่ดี แหล่งที่มาและไบนารีจากiMatix



2

รูปแบบหนึ่งที่ฉันชอบคือรูปแบบการออกแบบของรัฐ ตอบสนองหรือทำงานแตกต่างจากอินพุตชุดเดียวกัน
ปัญหาอย่างหนึ่งในการใช้คำสั่ง 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 สามารถรวมเป็นไฟล์เดียวเพื่อความเรียบง่าย


State3 และ State2 มีค่าส่งคืนแม้ว่าจะประกาศว่าเป็นโมฆะ
มด

1

จากประสบการณ์ของฉันการใช้คำสั่ง 'สวิตช์' เป็นวิธีมาตรฐานในการจัดการสถานะที่เป็นไปได้หลายสถานะ แม้ว่าฉันจะคาดเดาว่าคุณกำลังส่งผ่านค่าการเปลี่ยนแปลงไปยังการประมวลผลต่อรัฐ ฉันคิดว่าจุดรวมของเครื่องแสดงสถานะคือแต่ละรัฐดำเนินการเพียงครั้งเดียว จากนั้นการดำเนินการ / อินพุตถัดไปจะกำหนดสถานะใหม่ที่จะเปลี่ยนไป ดังนั้นฉันคาดหวังว่าฟังก์ชันการประมวลผลแต่ละสถานะจะดำเนินการตามที่ได้รับการแก้ไขทันทีเพื่อเข้าสู่สถานะจากนั้นจึงตัดสินใจว่าจำเป็นต้องเปลี่ยนไปยังสถานะอื่นหรือไม่


2
มีโมเดลต้นแบบที่แตกต่างกัน: เครื่องจักร Mealy และเครื่อง Moore การกระทำของ Mealy ขึ้นอยู่กับการเปลี่ยนแปลงของ Moore ขึ้นอยู่กับสถานะ
xmjx

1

มีหนังสือเล่มหนึ่งชื่อคือstatecharts ปฏิบัติใน C อย่างไรก็ตามมันเป็นวิธีที่มีน้ำหนักมากเกินไปสำหรับสิ่งที่เราต้องการ


2
ฉันมีปฏิกิริยาเดียวกันกับหนังสือเล่มนี้ ต้องใช้ 700+ หน้าเพื่ออธิบายและนำสิ่งที่ฉันคิดว่าใช้งานง่ายและตรงไปตรงมาได้อย่างไร!?!?
แดน

1

สำหรับคอมไพเลอร์ที่รองรับ__COUNTER__คุณสามารถใช้สำหรับการผสมสเตตัสแบบง่าย (แต่ใหญ่)

  #define START 0      
  #define END 1000

  int run = 1;
  state = START;    
  while(run)
  {
    switch (state)
    {
        case __COUNTER__:
            //do something
            state++;
            break;
        case __COUNTER__:
            //do something
            if (input)
               state = END;
            else
               state++;
            break;
            .
            .
            .
        case __COUNTER__:
            //do something
            if (input)
               state = START;
            else
               state++;
            break;
        case __COUNTER__:
            //do something
            state++;
            break;
        case END:
            //do something
            run = 0;
            state = START;
            break;
        default:
            state++;
            break;
     } 
  } 

ข้อดีของการใช้__COUNTER__แทนตัวเลขฮาร์ดโค้ดคือคุณสามารถเพิ่มสถานะที่อยู่ตรงกลางของสถานะอื่น ๆ ได้โดยไม่ต้องกำหนดหมายเลขใหม่ทุกครั้ง ถ้าคอมไพลเลอร์ไม่สนับสนุน__COUNTER__ในทางที่ จำกัด มันเป็นไปได้ที่จะใช้ด้วยความระมัดระวัง__LINE__


คุณช่วยอธิบายคำตอบของคุณเพิ่มเติมได้ไหม
หอยเป๋าฮื้อ

ใน mashine สถานะ "สวิตช์" ปกติคุณมีเช่น case 0, case 1, case 2, ... case 100 ถ้าตอนนี้คุณต้องการเพิ่ม 3 กรณีระหว่าง 5 ถึง 6 คุณต้องเปลี่ยนหมายเลขที่เหลือเป็น 100 ซึ่ง ตอนนี้จะเป็น 103 การใช้__COUNTER__ไม่จำเป็นต้องเปลี่ยนหมายเลขเนื่องจากพรีคอมไพเลอร์จะทำการกำหนดหมายเลขระหว่างการคอมไพล์
Seb

1

คุณสามารถใช้เฟรมเวิร์กเครื่องสถานะ UML ที่เรียบง่ายใน c https://github.com/kiishor/UML-State-Machine-in-C

รองรับทั้งเครื่องสถานะ จำกัด และลำดับชั้น มี API เพียง 3 ตัว, 2 โครงสร้างและ 1 การแจงนับ

เครื่อง State แสดงโดยstate_machine_tโครงสร้าง มันเป็นโครงสร้างนามธรรมที่สามารถสืบทอดเพื่อสร้างเครื่องของรัฐ

//! Abstract state machine structure
struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

สถานะแสดงด้วยตัวชี้ไปยังstate_tโครงสร้างในกรอบ

หากเฟรมเวิร์กถูกกำหนดค่าสำหรับเครื่องสถานะ จำกัด แล้วจะstate_tมี

typedef struct finite_state_t state_t;

// finite state structure
typedef struct finite_state_t{
  state_handler Handler;        //!< State handler function (function pointer)
  state_handler Entry;          //!< Entry action for state (function pointer)
  state_handler Exit;           //!< Exit action for state (function pointer)
}finite_state_t;

เฟรมเวิร์กจัดเตรียม API dispatch_eventเพื่อส่งเหตุการณ์ไปยังเครื่องสถานะและสอง API สำหรับการส่งผ่านสถานะ

state_machine_result_t dispatch_event(state_machine_t* const pState_Machine[], uint32_t quantity);
state_machine_result_t switch_state(state_machine_t* const, const state_t*);

state_machine_result_t traverse_state(state_machine_t* const, const state_t*);

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีใช้เครื่องสถานะลำดับชั้นโปรดดูที่เก็บ GitHub

ตัวอย่างโค้ด
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md
https://github.com/kiishor/UML-State-Machine-in -C / หยด / Master / สาธิต / simple_state_machine_enhanced / readme.md


คุณสามารถเพิ่มตัวอย่างโค้ดที่เหมาะกับคำถามได้หรือไม่
Giulio Caccin

1
โฟลเดอร์สาธิตในที่เก็บมีตัวอย่างเดียว github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/… . ฉันกำลังดำเนินการกับตัวอย่างระบบฝังตัวอีกหนึ่งตัวอย่างที่เกี่ยวข้องกับคีย์นำและตัวจับเวลา แต่ยังไม่เสร็จสมบูรณ์ จะแจ้งให้คุณทราบเมื่อพร้อม
Nandkishor Biradar


0

คำถามของคุณคล้ายกับ "มีรูปแบบการใช้งานฐานข้อมูลทั่วไป" หรือไม่ คำตอบขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุ? หากคุณต้องการใช้เครื่องสเตตที่กำหนดขนาดใหญ่ขึ้นคุณอาจใช้โมเดลและเครื่องกำเนิดสถานะ สามารถดูตัวอย่างได้ที่ www.StateSoft.org - SM Gallery Janusz Dobrowolski


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.