4.4 Expanding Purpose
Overview
Behaviors are intended to define various aspects of an application's literal behavior under various contextual circumstances. These circumstances and the functionality of the behavior through its operations define its purpose. Previously, the circumstances were only defined by the contexts themselves and any qualifications that the behavior may have for them, but there are settings for a behavior's contexts that can expand the purpose further.
These settings, described below, can be used individually or in combination to affect a change in how a behavior obtains or manages its contexts. The logging feature that has been worked on throughout this chapter can be expanded upon to better discuss what these settings can do. These changes not only work to better showcase the settings, but they also help realize one of the original intentions of logging, which was that logs could be routed to different outputs, not only the console as has been done so far.
First, there needs to be a context to differentiate console routing from the overall log settings:
The Log Settings
used so far is fine to define the underlying minimum log level, so the new context Console Log Route
can simply be a descendant of that. The Perform Logging
behavior should be updated for that as well:
There's not a lot of changes here, just swapping Log Settings
for Console Log Route
, renaming the behavior to Perform Console Logging
, and renaming the behavior's context settings
to route
. However, this decouples the behavior of console logging from the general Log Settings
.
Now that the logging functionality requires the Console Log Route
context instead of Log Settings
, the initial operation also needs to be updated. It may now look like the following:
These changes set the stage to move away from logging being console specific.
Exclusivity
The next step to enable multiple log routes pertains to the concept of the contexts' exclusivity, which defines how contexts might be shared between behaviors. The term "shared" itself is actually a keyword, shared
, to specify exclusivity, with its counterpart unique
. By default, all behavior contexts are unique per instance of the same type of behavior.
In other words, if there were two Console Log Route
contexts, there would be two Perform Console Logging
behavior instances and any Log Message
contexts that qualify for both would be split between them. Each Console Log Route
would be unique to their instance of the Perform Console Logging
behavior and each Log Message
would be unique to only one of those instances.
Shared
To address this issue, shared
can be specified for {*Log Message*}
, like so:
If a developer wants to be explicit, then unique
can also be specified for Console Log Route
without any change to this current functionality:
shared
should also be applied to the Maintain Console Log Route
as well, so that App State
could be shared across multiple route instances:
The main consideration to be made when sharing contexts is what purpose multiple instances of the shared context will serve for the affected behavior. Are there qualifications that will still distribute the different instances of a shared context across behavior instances or is it expected for there to really only be one such context in the application? Both are valid scenarios for different purposes of a behavior.
Fulfillment
While the logging feature has been getting a number of improvements, the overall flow of the application has been built to support it without much attention. Most notably, the initial operation of the application is kind of clunky and hasn't been improved for a while. Recall that it currently looks like the following:
Granted, all the Log Message
contexts that are activated would probably occur throughout other parts of the application, as would setting continue running
to False
, but a string of activations for long-running contexts, currently just App State
and Console Log Route
would only get longer as other routes are added. Behaviors can inherently take on the responsibility of those initializations through fulfillment settings.
Fulfillment pertains to how a behavior's contexts are fulfilled. By default, a behavior requires its contexts to be activated somewhere else in the application, and only when all of its required contexts are activated and available at the same time will a new behavior instance be created for them. This default setting is the existing
fulfillment. The Maintain App State
behavior can include the existing
keyword to make it explicit that the App State
must be activated elsewhere, although no functionality of the behavior will change by doing so:
Default
Another fulfillment setting is default
, which declares that the following context will be created with its default state as long as all other contexts of the behavior have been fulfilled. If there are no required contexts (contexts that must be fulfilled as existing
), then one instance of the behavior will automatically be created when the application starts with all of its default contexts. This works great for Maintain App State
, which can be changed to:
Since this behavior will be created when the app starts, it can also serve as a new entry point to the application. To embrace that responsibility, it can be refactored to include the initial operation:
The initialization operation here is kind of messy still, and it's peculiar for it to shut itself down during initialization. It can be simplified to one start up message and the trigger to shutdown can be tied to user input instead of assumed:
Now the application will initialize itself with an App State
and then activate a default Console Log Route
before logging its first message. It will then perform Run
, since app(continue running)
is True
by default, which will log another message and then wait until the user provides any input to the console. After input is received, Run
will log another message and set its own qualification condition, app(continue running)
, to False
which will subsequently initiate Shutdown
to be performed to deactivate the App State
, effectively stopping the application.
Optional
This is looking much better, and it sets the application up to have an input loop that can be used for the user to actually use the application. There's another small improvement that can be made to the application by using a third fulfillment setting. That setting is optional
, which declares that a context will be fulfilled by an existing context, when it is available, and until then it will be fulfilled by a default context if all of its other contexts are fulfilled.
The optional
keyword can be added to the Maintain Console Log Route
behavior declaration to make the Console Log Route
optional, like so:
With this change, Maintain Console Log Route
will create a default Console Log Route
once an App State
is made available, if there is not already a Console Log Route
for it to choose from. Note that even though App State
is shared
a default Console Log Route
will not be created in a second instance if a different Console Log Route
is already available, unless there is a second App State
that does not have an accompanying existing Console Log Route
.
Since Run
is already making a default Console Log Route
, that specific line can be removed, making its Initialize
operation simpler as just:
Once the application starts, it will create an App State
and see that doing so also fulfills the Maintain Console Log Route
behavior's required contexts, leading to the creation of a default Console Log Route
. This then leads to a few scenarios that can occur:
If
App State
were to make aConsole Log Route
in itsInitialize
, then that new context instance would automatically take the place of that defaultConsole Log Route
for all behaviors in which it has qualified, essentially overriding it.If an overriding
Console Log Route
were to be deactivated at some point, then a new default instance would be created to maintain the existence of the behavior for the persistingApp State
.If
App State
were to be deactivated, then any defaultConsole Log Route
would continue to persist (if it qualifies for another behavior) as if it had been activated on its own elsewhere in the application.
These rules lead to optional contexts behaving consistently with others, but with the added feature of being overridden and replaced to maintain their behavior's functionality. However, per these rules, the lifecycle and functionality of Maintain Console Log Route
is altered, specifically in that deactivate route
would deactivate the current default route, only to potentially have it replaced by another (were App State
to persist long enough for that to occur).
Since this behavior itself should depend on App State(continue running)
, it can qualify itself on that condition and force its (potentially default) route to be deactivated when it will no longer qualify:
Relation
The last setting is for specifying the relation of a context to the existence of the behavior itself. Generally, this only matters in terms of how a context may persist upon the destruction of the behavior, as occurs when one of its required contexts no longer qualify. The default setting of a context is for it to be independent
, to (attempt to) persist after a behavior has been destroyed by being maintained by another behavior. In terms of the logging feature, if not for Maintain Console Log Route
deactivating the Console Log Route
when the application is shutting down, the Console Log Route
would persist even though App State
was deactivated.
Coexistent
However, there is a clear dependency of Console Log Route
on the existence of App State
, and in particular the existence of Maintain Console Log Route
, which would be destroyed if App State
were deactivated. The coexistent
keyword can declare a context as only continuing to exist as long as all of its specified coexistent behaviors continue to exist. This setting can be applied to the Maintain Console Log Route
like so:
Now when App State
is deactivated, and the Maintain Console Log Route
behavior is destroyed, the Console Log Route
context will automatically be deactivated, which will also result in Perform Console Logging
being destroyed. This means the Stop Logging
operation can be reduced to a logging operation, to simply give feedback on what is occurring during a shutdown, whether by App State
being prematurely deactivated or the app going through the expected shutdown process:
Last updated