4.5 Adapting Behaviors
Last updated
Last updated
Just as contexts can be , so can behaviors, although in more limited ways. Adaptation in behaviors is focused on extending or replacing functionality and it is generally very similar to , with the purpose to either make it more specific in terms of its qualifications/contexts and available operations or change it entirely and prevent the original functionality when new qualifications are met. Doing either can be useful when working with third-party packages or when implementing conditional polymorphic functionality, as described below.
There have been a number of steps throughout this chapter to make the logging feature capable of handling multiple routes of logging; to output to the console or to one or more files. While that is possible with the current implementation, it would require multiple implementations of Perform Console Logging
for each different type of route, despite the over-arching behavioral logic (the contexts and qualifications) generally being the same.
The final needed to fully realize this objective will be discussed later, but the implementation can be updated with behavior adaptation to prepare for that. Specifically, the detail of exactly what a specific Console Log Route
does can be extracted from Perform Console Logging
and implemented through an extending behavior.
To review, consider the Perform Console Logging
behavior as it was last seen:
If the idea of a route is considered as an abstraction, it can be reasoned that the only important concepts are the minimum log level and what to do when a log message should be logged for that specific type of route. The minimum log level is something shared by all routes, so it can be part of any abstraction. What to do cannot be captured directly by a context's abstraction though, as it isn't data, it is functionality, so it is what will be re-implemented in an extending behavior.
To enable the extending behavior, Perform Console Logging
can consider any message that it would have logged itself as a Valid Log Message
and evaluate it, in anticipation that an extending behavior will add an operation to perform its specific type of logging for any Valid Log Message
:
An internal context is declared the same as any other context, but it is declared within the behavior, and it is only usable within the behavior and any extending behaviors. Any operation that depends on such a context can only be evaluated from within the behavior, since creating or aliasing any instances of that context is not possible from outside of the behavior.
Now the extending behavior can be implemented to perform the actual logging for a Valid Log Message
:
Perform Console Logging
has been re-created as the console-specific functionality that extends the more generalized Perform Route Logging
. Most of the declaration is the same, but the identifier now uses the stand-in notation to signify that Perform Console Logging
stands-in for (with the purpose of extending) Perform Route Logging
. This specifies that if a behavior instance of Perform Route Logging
has qualified for this Console Log Route
and this {*Log Message*}
, then this Perform Console Logging
is valid and will add its functionality onto Perform Route Logging
.
Every new route context type can have an accompanying Perform "Route Context" Logging
similar to Perform Console Logging
to handle the log messages in its specific way, while the overall filtering and iteration of those messages is left to the abstraction.
Extending behaviors offers a great way to build upon abstractions or more generalized functionality in a conditional way. An extending behavior can also extend multiple behaviors with and
or or
clauses within the stand-in notation, which provides a way to supplement multiple abstractions with the same code or to link two ancestor behaviors together with new functionality.
Where extending builds upon existing functionality with new functionality, replacement prevents the existing functionality from occurring, usually adding new functionality to take its place. This is useful for branching functionality for similar concepts in a conditional manner or for replacing functionality that is outside of the programmer's direct control.
For this example, consider the logging feature built so far as functionality offered by a third-party package that cannot be altered. It would be nice if logs were printed with the type of log preceding the message, instead of just Log:
, as is currently done. Unfortunately, the developer of the package did not implement it as such and does not offer a way to customize the messages. This sets the stage for a replacement to be implemented, such as:
With this replacement behavior, any time Perform Level Specific Console Logging
qualifies alongside a qualifying Perform Console Logging
, the Perform Console Logging
behavior will be ignored and only a Perform Level Specific Console Logging
behavior instance will be created. Its Log
operation will be performed the same as the Log
of Perform Console Logging
, except this new Log
operation is an operation hierarchy with operations that will handle each type of Log Level
to output the message with a specific prefix.
Just like extending behaviors, a replacement behavior is conditional. If desired, this replacement could have a qualification so that it only replaces Perform Console Logging
if some condition is met, perhaps a setting within Console Log Route
, or even some other context existing that Perform Console Logging
does not specify.
Operations can be replaced as well, with matching syntax and similar use. This can be useful within extending behaviors and for non-behavioral operations.
Perform Console Logging
has been renamed to Perform Route Logging
to indicate how it should be more generalized to any route (and will be made so ). It now has an internal context, Valid Log Message
. Any message that has passed the behavior's filter and can be iterated in Initiate Logging
is aliased as a Valid Log Message
and evaluated.
There are other ways that this could have been handled. Namely through a mapping of Log Level
to String
, where the String
is the prefix. Then the mapping could be performed and used to prepend the correct prefix without this behavior being concerned about the specifics of the Log Level
. That strategy would probably be preferable, either through a mapping accessible through an on the Log Message
context or a standalone mapping that is purely responsible for that logic as it relates to Log Level
.