Last updated 27/09/2020
A previous post discussed the benefits of using the command pattern to provide a service class with a safe and convenient way to allow other objects to invoke and monitor a sequence of operations (a service). This post describes an implementation of the command pattern that uses a class (T_Execution) that implements an interface (I_Execution). The source code for an example project can be found here. The Execution classes and interfaces are defined in the library tcl_BaseClasses. The source code for the library can be found on git hub here
The T_Execution class keeps track of the execution state of the service, and has methods to update the execution state and properties to show the current execution state in a convenient form. An instance of T_Execution is declared as an instance variable of the service class, and an interface pointer of type I_Execution is made available to the client code via a property
The execution state is held in the T_Execution instance by an enumerated value (T_ExecutionState), defined as follows:
- Ready indicates the service is not currently executing.
- Starting indicates that there has been an external request to start the service, but the service has not yet been initiated.
- Busy indicates that the service is currently executing.
- Aborting indicates that there has been an external request to cancel the current service, which should be handled internally to cleanup and return to a ready state.
The state machine is shown below. States are shown in green, methods called by the client via the I_Execution interface are shown in red and methods called by the service implementation are shown in blue:
The I_Execution interface is the interface provided to the client code to allow it to start and abort the service, and to clear the results.
The following methods are implemented:
If the current state is Ready, the result flags are cleared and the current state is set to Starting to request execution of the service. True is returned to signal that the start request was accepted.
If the current state is not Ready, an event is raised to provide a notification that the state was not the expected state, and False is returned (see later for a description of the error and aborted events)
If the current state is not Ready, then the current state is set to Aborting. Otherwise, the abort is silently ignored.
This method provides the calling object with a way to interrupt execution of the current operation and return it to a ready state. For this to work, the implementing class must detect the Aborting state and cancel its current operation. Once cancelled, it signals the completion of the abort using the SetAborted method which returns the current state to Ready
If the current state is Busy, an event is raised to provide a notification that the service was aborted (see later for a description of the error and aborted events)
The Clear method can be used to clear result flags, provided the current state is Ready. If the current state is not Ready, an event is raised to provide a notification that the state was not the expected state (see later for a description of the error and aborted events)
Methods not belonging to I_Execution
The following methods are provided to allow the service class to update the current execution state. They are excluded from the I_Execution interface as the state transitions they trigger need to be restricted to the service class implementation:
- SetBusy() – If the execution state is Starting, the Busy state is selected to indicate that the operation has started
- SetDone() – Sets the Done property and selects the Ready state to indicate that the operation has completed successfully
- SetError(ErrorMsg) – Sets the Error and ErrorMsg properties and selects the Ready state to indicate that the operation failed to complete successfully. An event is raised to provide a notification that an error occurred.
- SetAborted – Sets the Aborted property to indicate that the operation was aborted and selects the Ready state. An event is raised to provide a notification that the current service was aborted.
The following read-only properties are defined:
- Ready (BOOL)- current state is Ready
- Busy (BOOL) – current state is Busy
- Done (BOOL) – last operation completed successfully
- Error (BOOL) – last operation terminated with an error
- ErrorMsg (STRING) – error message describing the last error
- Aborted (BOOL) – last operation was aborted
- Notification_OnAbort – Interface pointer to the OnAbort notification. By default, this points to a global notification object in the GVL GlobalEvents, but can be set to zero to inhibit the instance from raising the OnAbort notification
- Notification_OnError – Interface pointer to the OnError notification. By default, this points to a global notification object in the GVL GlobalEvents, but can be set to zero to inhibit the instance from raising the OnError notification
The I_Execution interface that is available to the client class is exposed via a read-only property:
Because interfaces are reference types, we can access methods and properties directly on the interface property. i.e. External code can initiate the operation by calling Start() via the I_Execution interface, and check the progress of the operation by querying the state of the Busy, Done and Error properties.
Every instance of T_Execution has the ability to raise a notification when its SetError or SetAborted method is executed, or when the Start method is executed while the execution state is not ready. This is very useful when testing the software on a machine. See this post for an explanation of the notification framework.
The library that implement T_Execution has a global variable list called GlobalEvents. This holds a pair of notifications- Notification_OnError and Notification _OnAbort:
Each instance of T_Execution holds references to these notification instances, allowing them to raise the notification as necessary. These references sre intialised in the FB_Init method of T_Execution (and can be zeroed via the associated property to inhibit the event from being raised).
These notifications can be handled by instances of T_NotificationHandler in a Logger class which can then be used to send error messages, etc:
The Logging object is linked to the execution events as follows:
So with a few lines of code, all execution errors and aborts can be directed to a single destination for analysis.
The T_Execution class can offer a consistent and safe way for classes to provide an API to the operations they implement. It can track the state of an operation, provide a mechanism for aborting the operation and returning the class to a known state, and can provide diagnostics across the entire program to track errors in the execution of operations.