These patterns enable features of UML statecharts (heavyweight, unoptimized code) in QP (lightweight, optimized code).
We discuss each design pattern, giving the customary points for each pattern:
(1)The designer Intent for the pattern, (2) a statement of the
Problem the pattern is indended to solve, (3) a description of
the Solution guided by the pattern, (4) Sample
Code for a solution of an archetypical case, and (5) a discussion of
Consequences of use of the pattern.
Intent
- Provide common facilities and policies for handling events, but allow clients to override and specialize every aspect of behavior
Problem
- Provide overridable policy consistency
- Provide default behavior, overridable only when client chooses
- Provide a common look and feel in system-level software that the client applications can use easily as the default
- Clients must be able to override every aspect of the default behavior easily if they so choose
Solution
- Generally, apply concept of programming-by-difference
- Specifically, use behavioral inheritance
- Composite state defines default behavior and provides a "default behavior outer shell" for nesting client substates
- Client substates can override any default behavior
- Any events not handled by the client substates is handled by the default behavior of the composite state.
Sample Code 1
//................................................................... enum UltimateHookSignals { A_SIG = Q_USER_SIG, B_SIG, C_SIG, D_SIG }; //................................................................... class UltimateHook : public QHsm // define "default" behavior framework { protected: void initial(QEvent const *e); QSTATE generic(QEvent const *e); QSTATE specific(QEvent const *e); QSTATE final(QEvent const *e); public: UltimateHook() : QHsm((QPseudoState)initial) {} }; //................................................................... void UltimateHook::initial(QEvent const *) { Q_INIT(&UltimateHook::generic); } //................................................................... QSTATE UltimateHook::final(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: exit(0); return 0; } return (QSTATE)&UltimateHook::top; } //................................................................... QSTATE UltimateHook::generic(QEvent const *e) { switch (e->sig) { case Q_INIT_SIG: printf("generic:init;"); Q_INIT(&UltimateHook::specific); return 0; case A_SIG: printf("generic:A;"); return 0; case B_SIG: printf("generic:B;"); return 0; case C_SIG: printf("generic:C(reset);"); Q_TRAN(&UltimateHook::generic); return 0; case D_SIG: Q_TRAN(&UltimateHook::final); return 0; } return (QSTATE)&UltimateHook::top; } //................................................................... // define "specific" behavior - left to client program QSTATE UltimateHook::specific(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("specific:entry;"); return 0; case Q_EXIT_SIG: printf("specific:exit;"); return 0; case A_SIG: printf("specific:A;"); return 0; } return (QSTATE)&UltimateHook::generic; } //...................................................................
Sample Code 2
//................................................................... public class HookFrame extends JFrame { public HookFrame() { super("Hook Example"); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); setSize(300,300); } public static void main(String[] args) { HookFrame frame = new HookFrame(); frame.setVisible(true); } } //...................................................................
Sample Code 3
//................................................................... LRESULT CALLBACK WndMainProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_GRAPHNOTIFY: HandleGraphEvent(); break; case WM_SIZE: ResizeVideoWindow(); break; case WM_WINDOWPOSCHANGED: ChangePreviewState(! (IsIconic(hwnd))); break; case WM_CLOSE: // Hide the main window while the graph is destroyed ShowWindow(ghApp, SW_HIDE); CloseInterfaces(); // Stop capturing and release interfaces break; case WM_DESTROY: PostQuitMessage(0); return 0; } // Pass this message to the video window for notification of system changes if (g_pVW) g_pVW->NotifyOwnerMessage((LONG_PTR) hwnd, message, wParam, lParam); return DefWindowProc (hwnd , message, wParam, lParam); } //...................................................................
Consequences
- The specific substate needs to know only those events it overrides
- New events can be added easily to the top-level generic superstate without affecting the specific substate
- Removing or changing the semantics of events that clients already use is difficult
- Propagating every event through many levels of nesting can be expensive
Intent
- Make statechart topology more flexible by inventing event and posting to self
Problem
- Loosely related functions are often strongly coupled by a common event
- Polling for data, then processing the data
- Long sequence of events run-to-completion (RTC)
Solution
- Decouple by inventing an event and posting to self
Examples
- Polling for data, then processing (code)
- Network connection
- Listen for an incoming network connection
- When connection is attempted, perform processing to create the connection
- Message handling
- Series of messages decoded differently
- One state does nothing but listen for incoming messages and determines which method to decode by
- Appropriate event is posted to self, transitioning to individual decode states
- Breakup of long RTC steps
- Break one RTC into two or more
- Post event to self to initiate next step
Sample Code
//................................................................... enum SensorSignals { DATA_READY = Q_USER_SIG, TERMINATE }; //................................................................... class Sensor : public QHsm { public: Sensor() : QHsm((QPseudoState)initial) {} private: void initial(QEvent const *e); QSTATE polling(QEvent const *e); QSTATE processing(QEvent const *e); QSTATE idle(QEvent const *e); QSTATE busy(QEvent const *e); QSTATE final(QEvent const *e); private: int myPollCtr; int myProcCtr; BOOL isHandled; // flag indicates if last event was handled HWND myHwnd; // the main window handle friend BOOL CALLBACK reminderDlg(HWND hwnd, UINT iEvt, WPARAM wParam, LPARAM lParam); }; //................................................................... static Sensor app; static HINSTANCE inst; // this instance static HWND mainHwnd; // the main window static char appName[] = "Sensor"; //................................................................... void Sensor::initial(QEvent const *) { SendMessage(myHwnd, WM_SETICON, (WPARAM)TRUE, (LPARAM)LoadIcon(inst, MAKEINTRESOURCE(IDI_QP))); myPollCtr = 0; myProcCtr = 0; Q_INIT(&Sensor::polling); } //................................................................... QSTATE Sensor::final(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: EndDialog(myHwnd, 0); return 0; } return (QSTATE)&Sensor::top; } //................................................................... QSTATE Sensor::polling(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: SetTimer(myHwnd, 1, 500, 0); return 0; case Q_EXIT_SIG: KillTimer(myHwnd, 1); return 0; case Q_INIT_SIG: Q_INIT(&Sensor::processing); return 0; case WM_TIMER: SetDlgItemInt(myHwnd, IDC_POLL, ++myPollCtr, FALSE); if ((myPollCtr & 0x3) == 0) { PostMessage(myHwnd, WM_COMMAND, DATA_READY, 0); // post event to self } return 0; case TERMINATE: Q_TRAN(&Sensor::final); return 0; } if (e->sig >= Q_USER_SIG) { isHandled = FALSE; } return (QSTATE)&Sensor::top; } //................................................................... QSTATE Sensor::processing(QEvent const *e) { switch (e->sig) { case Q_INIT_SIG: Q_INIT(&Sensor::idle); return 0; } return (QSTATE)&Sensor::polling; } //................................................................... QSTATE Sensor::idle(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: SetDlgItemText(myHwnd, IDC_STATE, "idle"); return 0; case DATA_READY: // handle reminder event Q_TRAN(&Sensor::busy); return 0; } return (QSTATE)&Sensor::processing; } //................................................................... QSTATE Sensor::busy(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: SetDlgItemText(myHwnd, IDC_STATE, "busy"); return 0; case WM_TIMER: SetDlgItemInt(myHwnd, IDC_PROC, ++myProcCtr, FALSE); if ((myProcCtr & 0x1) == 0) { Q_TRAN(&Sensor::idle); } return 0; } return (QSTATE)&Sensor::processing; } //................................................................... extern "C" void onAssert__(char const *file, unsigned line) { char str[160]; wsprintf(str, "Assertion failed in %s, line %d", file, line); MessageBox(mainHwnd, str, appName, MB_ICONEXCLAMATION | MB_OK); exit(-1); } //................................................................... BOOL CALLBACK reminderDlg(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { QEvent e; e.sig = iMsg; switch (iMsg) { case WM_INITDIALOG: app.myHwnd = mainHwnd = hwnd; app.init(); // take the initial transition return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDCANCEL: e.sig = TERMINATE; break; default: e.sig = LOWORD(wParam); break; } // intentionally fall thru case WM_TIMER: app.isHandled = TRUE; app.dispatch(&e); return app.isHandled; } return FALSE; } //................................................................... int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmdLine, int iCmdShow) { InitCommonControls(); // load common controls library inst = hInst; // store instance handle DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG), NULL, reminderDlg); return 0; // exit application when the dialog returns } //...................................................................
Consequences
- Fix the QHM limitation on initial transitions, allowing initial transition to target substates
- Provides work-around for completion transitions
- Can break up CPU-intensive tasks (other events are given a chance)
- Note two ways to post: FIFO (at back of event queue) and LIFO (at front of event queue)
- Cost: more states to manage
Intent
- Simplify state machines by modifying the sequencing of events
Problem
- Reactive Systems must handle every event at any time
- Event may arrive at particularly inconvenient moment
- Nature of event may allow postponement
Solution
- Defer event, retain in separate event queue
- Handle it at more convenient time
- Effectively alters sequence of events presented to the state machine
Example: ATM
- Server Application receives many transactions
- Cannot throw away any transaction
- Defer incoming event
- Must automatically recall all deferred events as soon as possible
- Statecharts solution: special event queue for deferrals associated with every event type and active state (heavyweight)
- QP solution: re-post event to self (lightweight)
Sample Code: Windows GUI
//................................................................... class TServer : public QHsm { public: TServer() : QHsm((QPseudoState)initial) {} private: void initial(QEvent const *e); QSTATE operational(QEvent const *e); QSTATE idle(QEvent const *e); QSTATE receiving(QEvent const *e); QSTATE authorizing(QEvent const *e); QSTATE final(QEvent const *e); BOOL defer(QEvent const *e); // event deferral action void recall(); // recall action private: QEvent myDeferredRequest; BOOL isHandled; HWND myHwnd; // the main window handle friend BOOL CALLBACK tServerDlg(HWND hwnd, UINT iEvt, WPARAM wParam, LPARAM lParam); }; //................................................................... enum TServerSignals { TERMINATE = Q_USER_SIG }; //------------------------------------------------------------------- static TServer app; static HINSTANCE inst; // this instance static HWND mainHwnd; // the main window handle static char appName[] = "TServer"; //................................................................... BOOL TServer::defer(QEvent const *e) // implementation of defer action { if (IsDlgButtonChecked(myHwnd, IDC_DEFERRED)) // deferred? { return FALSE; // cannot defer any more events } myDeferredRequest = *e; // save the event (copy by value) CheckDlgButton(myHwnd, IDC_DEFERRED, BST_CHECKED); // deferred return TRUE; } //................................................................... void TServer::recall() // implementation of recall action { if (IsDlgButtonChecked(myHwnd, IDC_DEFERRED)) // deferred? { PostMessage(myHwnd, WM_COMMAND, myDeferredRequest.sig, 0); CheckDlgButton(myHwnd, IDC_DEFERRED, BST_UNCHECKED); } } // HSM definition --------------------------------------------------- void TServer::initial(QEvent const *) { SendMessage(myHwnd, WM_SETICON, (WPARAM)TRUE, (LPARAM)LoadIcon(inst, MAKEINTRESOURCE(IDI_QP))); Q_INIT(&TServer::operational); } //................................................................... QSTATE TServer::final(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: EndDialog(myHwnd, 0); return 0; } return (QSTATE)&TServer::top; } //................................................................... QSTATE TServer::operational(QEvent const *e) { switch (e->sig) { case Q_INIT_SIG: Q_INIT(&TServer::idle); return 0; case IDC_NEW: if (!defer(e)) // defer at composit level { Beep(1000, 20); // if event cannot be deferred, warn the user } return 0; case TERMINATE: Q_TRAN(&TServer::final); return 0; } if (e->sig >= Q_USER_SIG) { isHandled = FALSE; } return (QSTATE)&TServer::top; } //................................................................... QSTATE TServer::idle(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: SetDlgItemText(myHwnd, IDC_STATE, "idle"); recall(); // recall at descendant level return 0; case IDC_NEW: Q_TRAN(&TServer::receiving); return 0; } return (QSTATE)&TServer::operational; } //................................................................... QSTATE TServer::receiving(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: SetDlgItemText(myHwnd, IDC_STATE, "receiving"); SetTimer(myHwnd, 1, 1000, 0); // arrange for timeout in 1 sec return 0; case WM_TIMER: Q_TRAN(&TServer::authorizing); return 0; case Q_EXIT_SIG: KillTimer(myHwnd, 1); return 0; } return (QSTATE)&TServer::operational; } //................................................................... QSTATE TServer::authorizing(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: SetDlgItemText(myHwnd, IDC_STATE, "authorizing"); SetTimer(myHwnd, 1, 2000, 0); // arrange for timeout in 2 sec return 0; case WM_TIMER: Q_TRAN(&TServer::idle); return 0; case Q_EXIT_SIG: KillTimer(myHwnd, 1); return 0; } return (QSTATE)&TServer::operational; } //................................................................... extern "C" void onAssert__(char const *file, unsigned line) { char str[160]; sprintf(str, "Assertion failed in %s, line %d", file, line); MessageBox(mainHwnd, str, appName, MB_ICONEXCLAMATION | MB_OK); exit(-1); } //------------------------------------------------------------------- BOOL CALLBACK tServerDlg(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { QEvent e; e.sig = iMsg; switch (iMsg) { case WM_INITDIALOG: app.myHwnd = mainHwnd = hwnd; app.init(); // take the initial transition return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDCANCEL: e.sig = TERMINATE; break; default: e.sig = LOWORD(wParam); break; } // intentionally fall thru case WM_TIMER: app.isHandled = TRUE; app.dispatch(&e); return app.isHandled; } return FALSE; } //................................................................... int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmdLine, int iCmdShow) { InitCommonControls(); // load common controls library inst = hInst; // store instance handle DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG), NULL, tServerDlg); return 0; // exit application when the dialog returns }
Consequences
- Requires explicit defer and recall of deferred events
- Concrete (derived) state machines responsible for implementing defer and recall
- Multiple event types require independent queue
- Events are deferred at composit state level and recalled at descendant state
- Similar to Reminder, except:
- these are external events (not internally invented events)
- posting is to a separate deferred event queue (not the general queue)
Intent
- Use state machines as independent components
- State machine = stand-alone system
- Re-use
Problem
- Independent machine behavior best modeled by independent state machine
- UML Statechart Orthogonal Region is heavy and not comprised of independent (re-usable) components
Solution
- Use object composition instead of orthogal regions
- One machine (container) "has" the other as a data member (component)- Container communicates directly with component
- synchronous
- call dispatch() directly- Component communicates indirectly with container using Reminder pattern
- asynchronous
- post a message to container- Container and component share data where needed
Example: Alarm Clock
- TimeKeeping machine - stand-alone HSM
- Alarm
machine - stand-alone FSM- AlarmClock a modified TimeKeeping machine that has an Alarm data member
- AlarmClock has responsibility:
- for initializing Alarm
- for dispatching events to Alarm
Sample Code
(see text)
Consequences
- Communication
- Container communicates synchronously with Component
- Component communicates asynchronously with Container
- Data can be shared between Container and Component
- Tradeoffs
- Sharing Container data may be necessary at times but may reduce Component reusability
- OC pattern is not the best choice for a storage only problem due to added complexity
- Errors could result if the Container fails to dispatch events in some cases
- Benefits
- Orthogonal components are reusable within the application or across other applications
- OC Pattern can be leveraged to gain optimal performance if needed Component not limited to one level (i.e. can be HSM)
Intent
- Transition out of a composite state, but remember the most recent active substate so it can be returned to at a later time
Problem
- State transitions defined in high level composite states often deal with events that require immediate attention
- Event may force transition out of a composit state
- After handling the event, the system should return to the most recent substate of the given composite state
- A mechanism is needed to remember the last state
Solution
- Store the most recently active substate of the composite state in a dedicated attribute of type QState
- Set this attribute during the exit action from the composite state
- The transition to history of the interrupting state uses the attribute as the target of the transition
- Use dynamic transition, since history is determined at runtime
Examples
- Toaster Oven / door-open
- Printer / out-of-paper
- Washing Machine / unbalanced-load
Sample Code
class ToasterOven : public QHsm { public: ToasterOven() : QHsm((QPseudoState)initial) {} private: QState myDoorClosedHistory; }; QSTATE ToasterOven::doorClosed(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("door-Closed;"); return 0; case Q_EXIT_SIG: myDoorClosedHistory = getState(); return 0; } return (QSTATE)&ToasterOven::top; } QSTATE ToasterOven::doorOpen(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("door-Open,lamp-On;"); return 0; case Q_EXIT_SIG: printf("lamp-Off;"); return 0; case CLOSE_SIG: Q_TRAN_DYN(myDoorClosedHistory); return 0; } return (QSTATE)&ToasterOven::top; }
Consequences
- Requires that a separate QState pointer to member function is provided for each composite state to store the history of this state
- Requires explicitly setting the history variable in the exit action from the corresponding composite state using QHsm::getState()
- Requires the dynamic (not optimized) Q_TRAN_DYN(<history-variable>) transition macro to transition to the history of a given state
- Corresponds to the deep history, not the shallow history, pseudostate
- You can explicitly clear the history of any state by resetting the corresponding history variable