Introduction
As you develop systems in C++ Builder you will probably find dependencies between
the various data modules and forms are preventing you from attaining higher
levels of reuse. This document discusses some strategies for reducing or avoiding
those dependencies.
Types Of Coupling
Coupling among forms and data modules falls into a number of categories:
- Forms which refer to data sets in a data module (usually for lookups).
- Forms which call upon methods of other forms (often to get data from the
form, or to force the other form to update) or which reference data members
of other forms (again, usually to get data from the form).
- Data modules which update forms as a result of data changes.
- Data modules which call upon methods of controls on forms or reference controls
on forms.
- Data modules which refer to other data modules, usually for lookup or calculated
fields.
The coupling can be manifested in either:
- References to other form / data module components via properties of components
in this data module or form.
- References to other form / data module variables, methods, or components
by event handlers or methods in this data module or form.
Each type of coupling requires a different approach. Not all coupling can be
eliminated.
Using Design Reduce Coupling
Creating your system from decoupled forms and data modules is a good start.
Then, you can add the coupling to lower levels of a form inheritance hierarchy
and still preserve the independence of the top level.
In terms of designing data modules, it is always a good idea to start with
your ER diagram, and create a data module for each entity. Each entity data
module should be self contained - that is, it should contain:
- The primary table for the entity.
- All queries based on the entity primary table.
- All domain tables for the entity (that is, tables from which values for
attributes of the primary table will be drawn).
- All lookup tables for the primary table and the domain tables.
Each entity typically has a relationship with one or more entities. This is
where cross data module coupling first arises. There are two alternatives for
dealing with relationships.
- Make the relationship table a member of a third data module which is coupled
to the two or more related entity data modules.
- Make the relationship table a member of the entity data module.
An example of this sort of coupling exists when two entity primary tables must
be joined to produce data to be displayed in a grid. Obviously, this can be
accomplished either through a query or through a set of lookup fields on a primary
table which use another entity's lookup table as the lookup data set. In either
event, it is likely that the only inhabitant of a "relationship" data module
would be the query or the augmented primary table (either having lookup fields
referencing the non-primary table), or perhaps, the augmented primary table
and any needed lookup tables.
Many relationships are either uni-directional, or can be viewed as a pair of
uni-directional relationships (going in opposite directions). This allows either
strategy to work.
Generally, most relationships are "pull" relatioships, where only the current
entity knows enough to make the relationship work. For this reason, I prefer
strategy 2. As you will see later, this also has a good conceptual fit with
other elements of a decoupling strategy.
Using Data Awareness To Reduce Coupling
Data modules are the most sharable part of an application above the component
level. For a form to be coupled to a data module is not an undesirable form
of coupling, nor is it always one which can be eliminated, especially if you
wish to avoid cluttering your form with datasets and datasources.
The most common form of undesirable coupling involves a data module which updates
a form when a data event occurs. In almost all cases, this can be avoided through
the use of existing data aware components, or through the creation of a new
data aware component. For instance, if you need a label on a form which shows
the current number of rows in the current view of data from a dataset, you could
have the OnDataChange event handler for the dataset data source update the label.
Or you could derive a data aware label component, hook it to the data source
and then pull the record count from the associated data set. This is the preferred
course, since it reduces coupling, and where coupling exists, lays it on explicit
properties of the form.
Using Inheritance And Public Members To Reduce Coupling
Some situations may require a data source event handler or a data set event
handler to call upon a form, whether to update the form (for which data awareness
is the better solution) or to use the members of the form class or its components
to perform some computation or action. Coupling in this situation can be reduced
by having the data module or form maintain public members, preferably properties
(so lazy evaluation can be used) which are accessible from other form or data
module processing.
To make this reduce coupling, it is best used with a derived version of the
data module or form, with the ancestor having none of the offending code or
members. This improves reuse potential since the ancestor can be reused separately
from the coupled form or data module, and the coupling is clearly delineated
in the descendant.
Using The Redirector Pattern To Reduce Coupling
A redirector is a component or member whose sole function is to direct references
within code to what may be a different other component as time passes or in
different implementations. For instance, if a data module must access a form
control, that control can be assigned to a data module property by an application
form after both the form and the data module are instantiated. Or a component
can be used to embody the redirection, by having a property for the control
in question. This allows a default component to be set at design time, which
can then be easily overridden at run time (published properties cannot be added
to data module descendants, so this pattern overcomes that obstacle). In code,
the module references Redirector->Control rather than SomeForm->Control.
This pattern also makes it easier to construct test harnesses in the presence
of this sort of coupling.
Another useful aid provided by redirector components can be to have an event
handler which is invoked when the destination is changed. Such an event handler
can cascade the change to any other affected properties in components of the
form or data module, keeping knowledge of those properties within the form or
data module.
Summary
There is no panacea for reducing coupling. However, these techniques should
be helpful.
|