Design Pattern Case Study: A Simple Counter
by Quentin"Q" Alldredge, Q Software Innovations, LLC
What design pattern should I choose? The design pattern you choose is highly dependent on the project requirements. Here is a case study implementing a simple counter in many of the design patterns described in the Design Patterns Overview page.
Contents
Source code
All of the source code is available at:
QSI Shared Code on GitLab for LabVIEW 2018 For LabVIEW 2015
Requirements
The Program Shall:
- Provide a way to allow the user to choose between Increment and Decrement
- Provide a way to allow the user to Stop the program
- Provide an I32 Numeric Input called Amount
- Provide an I32 Numeric Output called Count
- When Increment is clicked, add Amount to the Count
- When Decrement is clicked, subtract Amount from the Count
- The UI must look like this (title can be different):
State Machine
The State Machine polls for user input which changes the state to Increment or Decrement. If both are FALSE the state is transitioned to Idle. A Wait is required but was made small to prevent any lag which the user could see. Undefined states cause an error and the loop to stop.
Pros | Cons |
---|---|
Simple to map flow in a flowchart and create | Either used with no UI or has to poll for user interactions which requires a Wait function to keep from taxing the processor |
Simple to extend by adding cases and modifying state transitions | Not meant for multiple processes that have to run at different rates |
Can't handle the window close button (disabled in this example) so there has to be something else to end program | |
UI becomes unresponsive if code takes a long time to execute | |
If using Strings to define state, the default case must be handled. It would be better to implement with an enum. |
Event Handler
The Event Handler operates only when there is user interaction. In this case it is when the user clicks Increment, Decrement, Stop or the Windows Close button, "X". The code for each operates inside of the Event Structure.
Pros | Cons |
---|---|
Does not have to poll for user interactions which reduces processor resources | Completely dependent on user interactions |
Can handle the windows close button event | Not meant for multiple processes that have to run at different rates |
UI becomes unresponsive if code takes a long time to execute |
Master/Slave
The Master/Slave separates the Event Handling into the Master Loop and the Data Processing into the Slave Loop. All the Master Loop does is issue commands while the Slave Loop executes those commands. If the Slave receives a command it doesn't know how to do it does nothing. The communication between them is a Notifier which is lossy, it can only send one command at a time. If a command is sent too quickly, it could be lost.
Pros | Cons |
---|---|
Does not have to poll for user interactions which reduces processor resources | Commands issued too quickly can be lost |
Can handle the windows close button event | If using Strings to define commands, the default case must be handled. It would be better to implement with an enum. |
Separates UI from code execution to keep it responsive | Commands do not have data or what to execute. It is up to the Slave Loop about what to do with each command. |
Can have multiple Slave Loops |
Producer/Consumer
The Producer/Consumer is closely related to the Master/Slave but instead of issuing commands the Producer/Consumer produces data in the Producer Loop which is the used, or consumed, in the Consumer Loop. The communication between them is a Queue which is will buffer the data so that none of it is lost.
Pros | Cons |
---|---|
Does not have to poll for user interactions which reduces processor resources | Can't have multiple Consumer Loops |
Can handle the windows close button event and other events | The structure of data is set for the Consumer Loop. |
Separates UI from code execution to keep it responsive | It is up to the Consumer Loop about what to do with the data. |
Buffers data so that none is lost |
Action Engine (AE)
The Action Engine (AE) implementation is similar to the State Machine design pattern. It uses an uninitialized shift register as a memory space for its data (the current count in this case study) and contains the actions to carryout ("Initialize", "Increment", "Decrement", "Stop", and Default). It does not implement the UI requirements and has to be used in an owning program which implements another design pattern. (However, if the above example of a QMH left the Count State shift-register uninitialized, the bottom Message Handling Loop could act as an Action Engine of sorts.)
Pros | Cons |
---|---|
Extensible Can add more actions. | If using Strings to define action, the default case must be handled. It would be better to implement with an type definition enum. |
Encasulates code to be used in multiple places but operates on the same data | Not usually used as a stand alone design pattern. Usually doesn't contain UI |
Queued Message Handler (QMH)
The Queued Message Handler (QMH) design pattern is a combination of Producer/Consumer, Event Handler and State Machine architectures together. It combines the command and data into a Message Queue that serves as the communication between the Event Loop and the Message Handling Loop. The QMH also defines communication back the other direction, from the Message Handling Loop to the Event Loop, through the use of User Events. Unhandled messages cause an error that stops the program.
Pros | Cons |
---|---|
Does not have to poll for user interactions which reduces processor resources | Can't have multiple Message Handler Loops |
Can handle the windows close button event and other events | If using Strings to define command, the default case must be handled. It would be better to implement with an enum. |
Separates UI from code execution to keep it responsive | |
Bi-directional communication between loops | |
Each command can have a different structure of data associated with it. |
Object-Oriented QMH
The Object-Oriented QMH uses Objects as the messages. These objects are defined by the Message class and include a must override method called Action. The Message Queue, Update Count User Event and Stop User Event are created in the State Initialize Method. Messages are sent using the Message Queue from the Event Loop to the Message Handling Loop where they are dequeued. The dequeued Message then calls its Action Method by dynamic dispatch which performs an Action on the State.
Pros | Cons |
---|---|
Extensible. It is easy to extend functionality by adding a new child of the Message Class. | Lots of files to manage. Every message is at least three files (Class, Action Method and Send Method). |
Messages contain the command (Class), the data (Class Private Data), and the action (Action Method). | Does not handle communication between modules without adding another communication method. |
Does not use strings to define commands. |
Delacor QMH
The Delacor Queued Message Handler (DQMH) design pattern is an extension of the QMH. Each module uses the same structure for messages as the QMH (cluster of command string, called Message, and data variant, called Message Data) but this time the queue is wrapped in an class called Message Queue. The DQMH also adds external communication between asynchronously running modules by using User Events. Adding new modules and new events are accomplished through built in scripting tools. A stand alone tester is also created along side each module.
When implementing this example the UI could have been part of the module. However, it seemed more correct to use a variation of the tester that was created as the UI. In this way it shows the user event communication. The design pattern of the tester and subsequently the UI VI is the Event Handler design pattern.
This requires the DQMH Tools from the LabVIEW Tools Network.
Pros | Cons |
---|---|
Extensible. Extend functionality using built in scripting tools executed from the Tools Menu. | Lots of auto-generated code with notes not to touch. Accidentally editing some things could break the scripting tools. |
Message creation and handling is managed via the tools. | Uses strings to define Messages. |
Provides a lot of in code documentation to help know how and where to modify. | It can be confusing when starting with this framework to know where to add your custom code. Be sure to follow documentation bookmarks. |
Adds external messaging between modules above that of the regular QMH. Which allows for multiple asynchronous processes. | |
Adds easy testing because a Tester API is created along side all modules |
Actor Framework
The Actor Framework design pattern is very similar to the Object-Oriented QMH. It also uses Objects as the Messages, inheriting from a special Message Class. The Messages also define the command (the message object itself), the data (the private data of the class), the action to carry out (the DO Method), and the way to send the message (the Send Method). The Message Handling Loop is completely abstracted away as the Call to Parent Actor Core Method. Main UI and the State is defined in the override of the Actor Core, shown below.
Pros | Cons |
---|---|
Extensible. It is easy to extend functionaily by adding a new child of the Message Class. | Lots of files to manage. Every message is at least three files (Class, Do Method and Send Method). |
Messages contain the command (Class), the data (Class Private Data), and the action (Do Method). | Difficult to debug. Using MGIs Monitored Actor helps. |
Does not use strings to define commands. | If editing the method a Message is based on, it doesn't update the message. This is fixed with MGIs Improved Actor Framework Message Maker. |
Similar to the OO QMH but adds external messaging between modules. Which allows for multiple asynchronous processes. | It can be confusing when starting with this framework to know where to add your custom code. |
Only one communication route. External modules can send the same Messages to a module that it can send to itself. |
QControls
This is definitely not the best use case for a QControl. QControls are Object-Oriented replacements for XControls. However, for science lets try it.
Like the FGV a QControl is not a stand alone operator; therefore, it needs to be in another VI to function. The Increment, Decrement, Amount changing, and the actual Count is handled by the QControl. The Stop button is handled by the owning UI. Count is updated via a User Event started by the QControl. Under the hood its just two Event Handlers; one for the QControl and one for the owning VI. The owning VI could be any design pattern. The QControl should not need anything more than an Event Handler but it could probably be a QMH if necessary.
Pros | Cons |
---|---|
Replaces XControls | It can be confusing, when starting, to know where to add your custom code. See Help->QSI->QControl Toolkit Help... |
Did I mention it replaces XControls? | Again, not the best use case for QControls. |