The Unofficial Newsletter of Delphi Users - by Mystic Software, Inc.


Captive Data Modules

Author: L. S. Lichtmann - LLichtmann@megabytesystems.com

Data modules were introduced in Delphi 2 to allow centralization of data access within an application. Anyone who ever had to do a global data structure change in a Delphi 1 application of more than modest size -- a string field width change, for example -- remembers the fun of having to open every form to check for the necessity of making a change. Those of us with this kind of joyful experience in our background greeted the arrival of the Data Module with open arms.

Unfortunately, there's a dark side to Data Modules: They reduce the modularity of larger programs. Modularity is a touchstone for programs large enough to require more than one programmer, or whose requirements are evolving. (In other words, for most programs.) The joy of having to wait while Wally finishes modifying a global data module that has grown as large as a bedsheet in order to add your own components surpasses even that of having to open every form, etc.

What to do? Of course, one could revert to dropping datasets and what-not specific to a particular form directly on that form. However, all those non-visual component can be a royal pain when you're trying to work with the visual elements of the interface. I like being able to tuck them away on a data module. Furthermore, it is much cleaner and more maintainable to have the code associated with the data components encapsulated in a data module and fully segregated from the user interface code.

To meet the desire to maintain modularity wherever possible, I've taken to equipping my forms with what I've come to refer to as "captive" data modules. The concept is simple enough: for each form with significant unique data requirements, I add a non-auto-create data module. As an insurance measure, I comment out the global reference variable. (If anybody else needs to access functionality on a captive data module, you want to provide a public method of the controlling form to make it available, or to think about relocating that functionality to a non-captive data module.) The controlling form is given a non-public field to reference the captive dm, which is instantiated in the FormCreate event:

TMyForm = class(TForm)
...
protected
    FDM: TMyDataModule;
...
end;

...
procedure TMyForm.FormCreate(Sender: TObject);
begin
    FDM := TMyDataModule.Create(Self);
end;

Note that it is possible to create and use captive data modules with controllers other than TForm descendents. For instance, I use this technique quite often with frames. The only modification required is that the instantiation of the data module will need to be done in an overridden constructor, as frames have no equivalent to the OnFormCreate event. I've also used captive data modules with controllers that are not TComponents, overriding both constructor (to instantiate, with a NIL owner) and destructor (to free the data module).

Captive data modules augment rather than replace standard global data modules. For example, you decidedly do not want to duplicate your connection components (TDatabase, TADOConnection, TSQLConnection, or whatever) on every captive data module. Components of this nature, and data components used throughout the application should remain on non-captive data modules.

You can continue to visually link datasets on captive data modules to connect on global ones. Personally, however, I view it as cleaner and safer to do things such as setting connections in code at runtime, after the instantiation of the captive module. The most convenient way to do this is to declare a public property of the appropriate type on the captive data module. The property should be provided with a Setter method that walks the components array of the module, checks for the appropriate type, and sets the connection where required. The FormCreate code of the controlling form then, assuming the BDE is use, will look something like...

    FDM := TMyDataModule.Create(Self);
    FDM.Database := GlobalDM.Database;

where the required database component has been exposed as a property of the global data module.