// Purpose. State design pattern versus table-driven approach // // Intent. Allow an object to alter its behavior when its internal state // changes. The object will appear to change its class. // // Discussion. The State pattern models state-specific behavior (easy to // add actions that accompany or augment the state transitions), whereas // a table-driven approach focuses on defining state transitions. // // vi editor escape letterI colon cReturn arrow // States ------ ------- ----- ------- ----- // Cmd BEEP Insert CmdLine nsc nsc (no state change) // Insert Cmd nsc nsc nsc BEEP // CmdLine Cmd nsc nsc Cmd BEEP #include #include class FSM; // State base class. Implements default behavior for all its methods. class FSMstate { // Could be: empty implementation, or error message. public: virtual void escape( FSM* ) { cout << " no state change" << endl; } virtual void letterI( FSM* ) { cout << " no state change" << endl; } virtual void colon( FSM* ) { cout << " no state change" << endl; } virtual void cReturn( FSM* ) { cout << " no state change" << endl; } virtual void arrow( FSM* ) { cout << " no state change" << endl; } protected: void changeState( FSM*, FSMstate* ); }; // State machine. Reproduces the State interface, and delegates all // behavior to the State derived class private data member. class FSM { public: FSM(); void escape() { _state->escape( this ); } void letterI() { _state->letterI( this ); } void colon() { _state->colon( this ); } void cReturn() { _state->cReturn( this ); } void arrow() { _state->arrow( this ); } private: friend class FSMstate; void changeState( FSMstate* s ) { _state = s; } // page 310 FSMstate* _state; // page 310 }; void FSMstate::changeState( FSM* fsm, FSMstate* s ) { // page 311 fsm->changeState( s ); } // State derived class. Each state overrides only the messages it responds to. class Cmd : public FSMstate { public: static FSMstate* instance() { if ( ! _instance ) _instance = new Cmd; return _instance; }; virtual void escape( FSM* ); virtual void letterI( FSM* ); virtual void colon( FSM* ); private: static FSMstate* _instance; }; FSMstate* Cmd::_instance = 0; class Insert : public FSMstate { // State derived class public: static FSMstate* instance() { if ( ! _instance ) _instance = new Insert; return _instance; }; virtual void escape( FSM* ); virtual void arrow( FSM* ); private: static FSMstate* _instance; }; FSMstate* Insert::_instance = 0; class CmdLine : public FSMstate { // State derived class public: static FSMstate* instance() { if ( ! _instance ) _instance = new CmdLine; return _instance; }; virtual void escape( FSM* ); virtual void cReturn( FSM* ); virtual void arrow( FSM* ); private: static FSMstate* _instance; }; FSMstate* CmdLine::_instance = 0; void Cmd::escape( FSM* fsm ) { cout << " BEEP" << endl; } void Cmd::letterI( FSM* fsm ) { cout << "Insert:" << endl; changeState( fsm, Insert::instance()); }; void Cmd::colon( FSM* fsm ) { cout << "CmdLine:" << endl; changeState( fsm, CmdLine::instance()); }; void Insert::escape( FSM* fsm ) { cout << "Cmd:" << endl; changeState( fsm, Cmd::instance()); }; void Insert::arrow( FSM* fsm ) { cout << " BEEP" << endl; } void CmdLine::escape( FSM* fsm ) { cout << "Cmd:" << endl; changeState( fsm, Cmd::instance()); }; void CmdLine::cReturn( FSM* fsm ) { cout << "Cmd:" << endl; changeState( fsm, Cmd::instance()); }; void CmdLine::arrow( FSM* fsm ) { cout << " BEEP" << endl; } // Start state is Cmd FSM::FSM() { cout << "Cmd:" << endl; changeState( Cmd::instance() ); } void main() { FSM vi; char input[20]; while (1) { cout << " "; cin >> input; if ( ! strcmp(input,"esc")) vi.escape(); else if ( ! strcmp(input,"i")) vi.letterI(); else if ( ! strcmp(input,":")) vi.colon(); else if ( ! strcmp(input,"cr")) vi.cReturn(); else if ( ! strcmp(input,"arr")) vi.arrow(); } } // Cmd: CmdLine: // i arr // Insert: BEEP // : i // no state change no state change // arr cr // BEEP Cmd: // esc cr // Cmd: no state change // : esc // BEEP // Purpose. Table-driven approach to state-based design // // Discussion. Cargill ("C++ Programming Style") uses a table-driven // approach to impose structure on state-driven code. For each state, a // table maps every possible input to a succeeding state. In effect, this // approach converts conditional code (and virtual functions, in the case of // the State pattern) into a table look-up. The main advantage of tables is // their regularity: you can change the transition criteria by modifying data // instead of changing code. The main disadvantages are: table look-up is // less efficient than a virtual function call, the transition logic is less // explicit (harder to understand), and it is difficult to add actions to // accompany the state transitions. The key difference between the two is: // the State pattern models state-specific behavior, whereas the table-driven // approach focuses on defining state transitions. #include #include enum States { Cmd, Insert, CmdLine }; enum Events { Escape, LetterI, Colon, CR, Arrow }; class State; typedef void (State::* Action)(); #define MAX_STATES 3 #define MAX_EVENTS 5 class State { public: State( char* name ) { strcpy( name_, name ); } void enter() { cout << name_ << ":" << endl; } void beep() { cout << " BEEP" << endl; } void nsc() { cout << " no state change" << endl; } protected: char name_[10]; private: friend class FSM; State* transition_[MAX_EVENTS]; //////// Here is the "table" ///////// Action before_[MAX_EVENTS]; //////// Here is the "table" ///////// Action after_[MAX_EVENTS]; //////// Here is the "table" ///////// }; struct Mapping { States from; Events input; States to; Action before; Action after; }; class FSM { public: FSM( Mapping* map) { graph_[0] = new State( "Cmd" ); graph_[1] = new State( "Insert" ); graph_[2] = new State( "CmdLine" ); for (int i=0;ifrom])->transition_[map->input] = graph_[map->to]; (graph_[map->from])->before_[map->input] = map->before; (graph_[map->from])->after_[map->input] = map->after; } current_ = graph_[0]; current_->enter(); } //////// traverse the table //////// void next( char* input ) { Action action; //////// Do "before" action //////// action = current_->before_[convert(input)]; if (action != NULL) (current_->*action)(); //////// Prepare for "after" action //////// action = current_->after_[convert(input)]; //////// Do state transition //////// current_ = current_->transition_[convert(input)]; //////// Do "after" action //////// if (action != NULL) (current_->*action)(); } private: State* graph_[MAX_STATES]; State* current_; Events convert( char* input ) { if ( ! strcmp( input, "i" )) return LetterI; else if ( ! strcmp( input, ":" )) return Colon; else if ( ! strcmp( input, "arr" )) return Arrow; else if ( ! strcmp( input, "esc" )) return Escape; else if ( ! strcmp( input, "cr" )) return CR; else return CR; } }; class FsmSample : public FSM { public: FsmSample() : FSM( cells ) { } private: static Mapping cells[ MAX_STATES * MAX_EVENTS ]; }; //////// Here is the table "data" //////// /* static */ Mapping FsmSample::cells[] = { {Cmd, Escape, Cmd, &State::beep, NULL}, {Cmd, LetterI, Insert, NULL, &State::enter}, {Cmd, Colon, CmdLine, NULL, &State::enter}, {Cmd, CR, Cmd, &State::nsc, NULL}, {Cmd, Arrow, Cmd, &State::nsc, NULL}, {Insert, Escape, Cmd, NULL, &State::enter}, {Insert, LetterI, Insert, &State::nsc, NULL}, {Insert, Colon, Insert, &State::nsc, NULL}, {Insert, CR, Insert, &State::nsc, NULL}, {Insert, Arrow, Insert, &State::beep, NULL}, {CmdLine, Escape, Cmd, NULL, &State::enter}, {CmdLine, LetterI, CmdLine, &State::nsc, NULL}, {CmdLine, Colon, CmdLine, &State::nsc, NULL}, {CmdLine, CR, Cmd, NULL, &State::enter}, {CmdLine, Arrow, CmdLine, &State::beep, NULL}, }; void main() { FsmSample fsm; char input[20]; while (1) { cout << " "; cin >> input; fsm.next( input ); } } // Cmd: CmdLine: // i arr // Insert: BEEP // : i // no state change no state change // arr cr // BEEP Cmd: // esc cr // Cmd: no state change // : esc // BEEP