|
Increase Productivity and Reliability of your software with a set of C++ class templates.
Any non-trivial software program faces the challenge of
keeping all parts of the program in a consistent state. Consider
this scenario: Your software allows you to view results of a
database query. Those results may be seen in more than one view.
In the upcoming revision you decide to now give your customer a
new option: display headers with normal face or bold face. We assume that this new feature
is implemented with a Boolean
variable whose value determines if the headers are printed
bold or not.
What are the possible approaches to
have all views of the report be updated when the variable changes? Here are a few:
-
Whatever part of your program modifies the
variable is also required to call a method in each view. This
approach requires that from now on every time this variable is modified
anywhere, your programmers must also call all views to get them
updated. It also means that if you decide to create a new kind
of view that you have to go to every part of your program that
modifies the variable and upgrade the code so that this
new view is properly handled.
-
Provide a mutator function and make sure
that the variable can only be modified through this function.
Now there is only one place in your code where the view
notification must be maintained. However, each of
your variables needs a mutator function which pretty
much replicates what the other ones do.
-
Provide a class that encapsulates the
variable and that has a notion of attached "views"
that it needs to notify when its value changes.
Furthermore, in C++ the use of templates make it possible to
write the code for this class once (as a template) and use it
for different types of variables. Component Software has
developed two sets of class templates for this purpose. This
article describes both sets.
WiredVar / NotificationClient family of
classes
WiredVarT<> is a template, which
contains one member (m_v) of type T. M_v contains the current
value of the variable. Assignment operator and T operator are
provided so that the WiredVar can be used as if it were a
simple variable of type T. WiredVarT subclasses from WiredVar.
WiredVar maintains a list of notification clients. The job of
WiredVarT is to use WiredVar whenever the value of m_v changes.
NotificationClient is an abstract class
which works with WiredVar.
Classes that want to become notification clients must implement
this interface. Of particular importance is the VarChanged method
in NotificationClient. It is this method that WiredVar will
call when the content of its m_v changes.
Notification clients register and unregister with the wired variable through the RegisterClient
and UnregisterClient methods. This can be done at any point in the
lifetime of the program.
While a notification client is registered with
a wired variable, it will receive update notifications on its
VarChanged method. Since a notification client may be registered
with more than one wired variable, Varchanged may be called from
multiple notification clients at different times. To allow
identification identification, VarChanged contains
a pointer to its caller.

Wire aggregator class
An extension of the wired variable /
notification client family is the wire aggregator. Its job is to
collect notifications for a number of wired variables into one single notification. Using an aggregator class has the
advantage that there is only one place in the program where a new
wired variable must be added to a notification client, and many
notification clients will instantly be notified of changes in the
new wired variable.
Consider this example: Suppose that you have a
program that has a number of variables implemented as
wired variables. Without the wire aggregator, each view would need
to register with each wired variable. If a new wired variable is
added to your program, all views will need to be upgraded to now
register with this new variable. With the wire aggregator, the
views only register with the wire aggregator, and the wire
aggregator is the only object that must be upgraded to now
register with the new wired variable. When the wire aggregator
calls its notification clients, it puts a pointer to itself into the
call to the VarChanged method. Experience has shown that this is practical in most, but
not all cases. For this reason, the VarChanged has two arguments:
The first one is the immediate caller of VarChanged, the second
argument is usually used to
communicate the wired variable that has caused the
VarChanged. If the notification client has a need to know the
original wired variable, it can use the second argument for that
purpose.

GUI adapters for notification clients
Wired variables have been used in a number of
projects at Component Software. Some of these projects are GUI
applications. To facilitate the use of wired variables in GUI
views, a set of adapters have been created to connect a wired
variable to a control on the screen. In particular, there have
been adapters for text edit boxes, list boxes, combo boxes, and
sliders. Their implementation is in two stages: The first stage,
implementing the logical functionality and the interface to the
wired variable is done as a partially-implemented class (WiredControl).
One of its methods, UpdateGUI, is a pure virtual class, which must be
implemented by the operating system. This approach allows the GUI
adapters to be used in different graphical environments (MS
Windows/MFC and gtk are currently supported, Mac OSX Carbon is in
the development stage) with minimal OS-specific code. WiredControl
allows subclasses access to their associated WiredVar. This is
typically used by event notification handlers to modify the
WiredVar in response to user modifying data through the program's
GUI.

Param/AgentOfSupervisor/Supervisor class
family
A second set of class templates are the ParamT,
AgentOfSupervisor, and Supervisor templates. Their use is similar
to the wired variable/ notification client, but differs in the
fact that the relationship between ParamT and one or more
AgentOfSupervisors is established at construction time and not
changeable. Correct construction is enforced by the compiler.
ParamTs also don't have to have a "home." They are
implemented in a way that they exist as long as there is 1 or more
AgentOfSupervisors that are attached to it. Once the last attached
AgentOfSupervisor is destroyed, the ParamT is destroyed with it.
AgentOfSupervisors have a SetV and GetV
function, rather
than assignment and T operators. In the case of the application
for which this
class family was created it was a better approach, but for other
applications the SetV and GetV methods can easily be replaced by
operators. AgentOfSupervisors have a Supervisor. This relationship
is established during construction. Typically an AgentOfSupervisor is
placed as a member in a class, and that class then is the
Supervisor.

Source code
The source code (available for Windows/MFC,
Unix/X11/gtk, and Mac Cabron) implements a small example of the
WiredVar/Notification client class template, in which observers in
multiple views are attached to multiple wired variables.
Bibliography
Design Patterns, Gamma et. al. ISBN
0-201-63361-2
|