C + + design pattern - state pattern

State mode

Allows an object to change its behavior when its internal state changes. Object appears to have modified its class-- Baidu Encyclopedia

Simply put, state pattern is state machine design. It is mainly used for the same request to perform different functions under different conditions. Its function is equivalent to if else.

significance

In the actual coding work, we often encounter some special scenes with different results from ordinary scenes, forcing us to use if to avoid. With the passage of time, these IFS are used more and more, resulting in the code being obscure and difficult to understand, becoming a "lump" and difficult to maintain. At this time, we should analyze under which special conditions these special scenarios are generated, whether they can be separated from ordinary scenarios, and add this scenario without affecting the main process. After understanding these conditions, we can introduce state machine to refactor the code.

scene

During the operation of the elevator, there is a need to get on, get off and wait for the elevator at any time. These requirements will produce different actions when the elevator is idle, up or down. For example:

  • Assuming that the elevator is on the second floor and idle, you need to take the elevator when you encounter the first floor. At this time, the elevator should go down to the first floor.
  • Suppose the passenger is in the elevator on the 2nd floor and presses the 5th floor in the elevator. At this time, he meets the 1st floor and takes the elevator. At this time, the elevator should first go up to the fifth floor and then down to the first floor.

For the same demand, different actions are generated due to different elevator operation states. So, if you use code to realize the operation of elevator?

analysis

According to the usual coding habit, an elevator running thread is usually written, and various IFS are added to the thread to deal with d if ferent scenarios. There are some hidden dangers in this design:

  • All scenes are mixed together, resulting in confusion and easy to miss special scenes.
  • Conflicts in some scenarios cannot be found in time, resulting in unexpected behavior bugs.
  • if is widely used, which makes it difficult to clarify the code running process and maintain.

At this point, these scenarios can be clearly described by referring to the state machine:

  • First clarify the operation status of the elevator, which can be divided into uplink, downlink, idle and fault.
  • Then find out what needs the elevator will encounter, mainly: make an appointment to take the elevator on the current floor, make an appointment to take the elevator and get off the elevator under the current floor.
  • Finally, sort out the processing methods of these requirements in different states and the switching conditions of elevator States, and all scenarios can be constructed.

state diagram

  • ① Startup operation
  • ② There is demand above the current floor
  • ③ There is no riding demand at present
  • ④ There is no demand above the current floor, but there is demand below the floor
  • ⑤ There is no demand under the current floor, but there is demand above the floor
  • ⑥ There is a demand under the current floor
  • ⑦ There is no riding demand
  • ⑧ Elevator active switching stop state
  • ⑨ Elevator actively switches to idle state

code

Due to the large amount of code, this article only posts some core code fragments:

Status table

CElevatorSrv::mStateTable[] =
{
    { LEV1_ANY,         LEV2_ANY,   SIG_ID_POWER_ON,        &CElevatorSrv::MsgRespondInit},
    { LEV1_DOOR_OPEN,   LEV2_IDLE,  SIG_ID_POWER_OFF,       &CElevatorSrv::MsgRespondShutdown},
    { LEV1_DOOR_CLOSE,  LEV2_IDLE,  SIG_ID_TAKE_UP_ORDER,   &CElevatorSrv::MsgRespondOrderUpIdle},
    { LEV1_ANY,         LEV2_ANY,   SIG_ID_TAKE_UP_ORDER,   &CElevatorSrv::MsgRespondOrderUp},
    { LEV1_DOOR_CLOSE,  LEV2_IDLE,  SIG_ID_TAKE_DOWN_ORDER, &CElevatorSrv::MsgRespondOrderDownIdle},
    { LEV1_ANY,         LEV2_ANY,   SIG_ID_TAKE_DOWN_ORDER, &CElevatorSrv::MsgRespondOrderDown},
    { LEV1_ANY,         LEV2_ANY,   SIG_ID_ARRIVE_FLOOR,    &CElevatorSrv::MsgRespondArriveFloor},
    { LEV1_ANY,         LEV2_IDLE,  SIG_ID_EXIT,            &CElevatorSrv::MsgRespondExit},
    { LEV1_ANY,         LEV2_ANY,   SIG_ID_ANY,             &CElevatorSrv::MsgRespondIgnore}
};

Note: in the actual scene, the elevator can only operate when it is closed. Therefore, the door opening and closing are regarded as the primary state, and the elevator operation state is regarded as the secondary state.

Message processing function

void CElevatorSrv::ProcessMsg(SMsgPacket *pMsg)
{
    if (!IsStart()) {
        ELEVATORSRV_LOGE("Elevator not start!\n");
        return;
    }

    if (pMsg == nullptr) {
        ELEVATORSRV_LOGE("pMsg is nullptr!\n");
        return;
    }

    int index = 0;
    EElevatorDoorState curLev1State = GetLev1State();
    EElevatorRunState  curLev2State = GetLev2State();
    EMsgType msgId = pMsg->type;

    ELEVATORSRV_LOGD("Get Msg: 0x%x\n", msgId);
    // loop: traverse the state table and enter the entry matching the state
    do
    {
        if (   (   (mStateTable[index].lev1State  == curLev1State)
                || (mStateTable[index].lev1State  == LEV1_ANY)
               )
            && (   (mStateTable[index].lev2State  == curLev2State)
                || (mStateTable[index].lev2State  == LEV2_ANY)
               )
            && (   (mStateTable[index].msgId      == msgId)
                || (mStateTable[index].msgId      == SIG_ID_ANY)
               )
           )
        {
            (this->*(mStateTable[index].callback))(pMsg);
                break;
        }
        index++;
    } while (1);
}

Enter the response function matching the current state by looking up the table.

summary

  • The implementation method of state mode is not difficult, mainly due to the rise of programming ideas. Binding states to requirements can not only realize different response modes of unified requirements, but also do not respond to specified requirements in some states. Clear scene and clear thinking.
  • When you need to add a status or requirement, you only need to add it in the table without modifying the existing logic, which conforms to the opening and closing principle.
  • The use of state mode can focus only on the complete process generated by the requirements encountered in the current state. There is no need to consider the impact of other states.
  • Decoupling requirements from responses also enables communication management. For example, for the demand response between different processes, the communication between them can be designed as cross process communication that does not involve business, so as to realize the reusability of communication code.
  • In general, state mode is a very practical design mode. Not only in terms of code, but also in terms of design ideas, so as to reduce the sorting work of designers for complex business. Same perfection!

Keywords: C++ Embedded system OOP architecture

Added by atlanta on Tue, 04 Jan 2022 06:43:54 +0200