In the last post we started discussing selection forms and I stopped at the point where we were encountered a dilemma: the form is constructed with the form.Create() method but there’s data that’s not loaded into it yet. So how do you get the data into the form?
I’d like to take a slight detour for a minute and discuss something called dependency injection. Wikipedia defines dependency injection thusly:
Dependency injection is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object (or client) and are made part of the client’s state.
Any time you need to pass data into an object, it represents this design pattern. Some folks might quibble about whether the dependencies can only be “services”, or whether they need to be “passed by reference”. For this discussion, it doesn’t really matter — the point is, you have some data (fields, objects, or services) that needs to be injected into the object (a TForm in this case) and it affects the state of that object. When a form opens and you want the user to be looking at a bunch of fields already populated with data to select from, that is the initial state of the form.
So the question is, what’s the proper way of interacting with the form so it is initialized properly when the user sees it? Let’s look at our options within the context of the dependency injection pattern.
CONSTRUCTOR INJECTION VS. PROPERTY INJECTION
In the world of dependency injection, there are two schools of thought around this. They are called “constructor injection” and “property injection”. (There’s also “method” and “field” injection, but I regard them as sub-cases of property injection.)
The first school of thought, constructor injection, says you want to treat the construction of an object (like a form) as an atomic operation. Therefore, you should submit all of the form’s dependencies (ie., initial field values or a service that provides access to them) as arguments to the constructor. That way, when the constructor completes executing, the object (eg., the form) is fully and completely initialized.
The second approach, called property injection, allows you to call the constructor to create the object, but then you inject dependencies through properties.
When you’re looking at a typical object created in Delphi applications (as in the VCL), you can see both of these in action. Objects typically have one or more constructors, and frequently have several properties defined. But you’ll notice that most situations don’t come anywhere close to defining every possible property value in the constructor’s argument list.
Properties are typically used to alter the state of the object after it has been constructed.
However, when it comes to forms, you’ll typically notice no constructor is defined (since it’s inherited from TCustomForm), and … no properties. You can look at one form after another and see the same thing over and over.
Which leaves you with one glaring question: how in the heck do people inject data into their forms?
METHOD #1 — CONSTRUCTOR INJECTION
The first way is to go with constructor injection by defining a new constructor that overrides the default and adds an exhaustive list of all of the initial state variables as arguments that the form needs.
constructor Create( aOwner: TComponent; aArg1, aArg2 : string; aArg3 integer ); override;
This is ok if you don’t have many items. (Some folks argue that more than three or four is all you really want.) But suppose you want to submit the list of NFL Leagues and teams in each league? Well, that could be passed in something like a TStringList.
And the problem with that is, you’re tightly-coupled to that particular data object or container. (Some folks will argue that this is not really a problem, which is fine. It simply prevents you from changing the type of container used in the future. There are ways to address this, which we’ll discuss in a later post.)
constructor Create(aOwner: TComponent; aNFLData: TStringList); override;
The problem with this approach is that these declarations generate compiler warnings without adding the reintroduce keyword to the declaration. And while you may think it’s no big deal, you’ve just broken the virtual inheritance heirarchy for TForms (via the reintroduce specifier).
METHOD #2 — PROPERTY INJECTION
Property injection requires something else you don’t normally see in forms: properties. Hey, a form is an object, and it can have properties. It’s just that … you rarely see properties defined in forms because nobody uses them. Hmmm… why is that?
Well, too much of the time it’s because people revert to Method #3, which is described next.
But first let me say that properties decouple the interface from the implementation, as will be discussed shortly. This is a very important principle of OOP, and it’s a Good Thing to consider.
The major drawback that some folks have with property injection is that it’s done after the object is constructed, and there’s no way for the compiler to check whether you’ve initialized all of the necessary fields or not. With constructor injection, your program won’t compile if you leave off any arguments.
METHOD #3 — BACKDOOR INJECTION
Ok, I made this term up just now — backdoor injection. This is when the code that is preparing to open a form calls its constructor to create it, then goes in and pokes values directly into the fields on the form. So if a form named form1 has some TEdit fields in it named, edtLastName and edtFirstName, then client code using backdoor injection might look something like this:
form1 := TForm1.Create( self ); form1.edtFirstName := LFirstName; form1.edtLastName := LLastName;
Of course, you would probably NEVER, EVER do this with any kinds of objects … except Forms. What’s up with that? Unfortunately, I’ve seen this more times than I care to think. (And yes, I’ve even been guilty of doing it myself from time to time, simply because it’s so easy.)
The problem with this approach is that the client code is now VERY TIGHTLY COUPLED to the INNER-WORKINGS (ie., the implementation) of the TForm1 object. So if you ever change one of these TEdit fields to something else, you’ve just broken all of the code that interacts with this form. Yuk.
“Why might one do that?” you ask. Well, say you have a TEdit field named DateOfBirth and someone at the client’s asks, “Can you add a pop-up calendar to that field?” Why, sure! All we need to do is change it from a TEdit to a TDateTimePicker, and Viola! A pop-up calendar!
And all of your backdoor injection code that uses this form just broke! Ouch.
Suppose you have a property instead that’s defined (externally) as a string:
property DateOfBirth : string read GetDOB write SetDOB;
The GetDOB and SetDOB methods will typically interact with the value stored in the edit component itself. That means, when you read DateOfBirth by assigning it to a local variable, you’ll get the value currently in the field; and when you assign a value to DateOfBirth, you’re setting the value in the field — that is, the field on the form that’s visible to the user.
In this case, the GetDOB and SetDOB methods can be easily modified to accomodate the changed implementation on the form of the DOB field. Nobody outside the class is affected if you use properties, because they insulate clients of a class from the inner-workings, as well as having to know how things are actually implemented. This is a Good Thing, and why properties are an ideal solution for insulating clients from implementation details inside of a class.
(And notice that’s not the case with constructor injection. So score another point for property injection!)
METHOD #4 — DATABASE INJECTION
If you’re working with databases which afford you the additional luxury of using data-aware controls, you may (hopefully!) have a DataModule defined for each form that uses them. So you don’t need to pass anything special to the form’s constructor, but you do need to initialize the DataModule and somehow get the security credentials passed to it.
But, here’s the thing: unless you’re going to work with the entire data table and whatever is in it, then you probably need to pass in some parameters in order to filter the query to get the data you want: Invoice#, ClientID, Transaction#, whatever. And we’re right back to either constructor or property injection. (You can use stored queries, but they’ll still require parameters to be passed-in for most situations.)
Also, using LiveBindings with objects presents the same issue — you need to inject the objects containing the data into the form or DataModule, rather than database credentials.
(This also shifts the issue from the form to its associated DataModule; it doesn’t make it go away; rather, it just drops it on another’s doorstep.)
Constructor injection is ok if you’ve got a few fields, or an object that you can pass in. But at some point it can become unwieldly. The advantage it has is that the compiler can warn you if you’re missing any arguments. But this benefit goes out the window if you start stuffing data into objects (eg., TLists) to keep from having excessively long argument lists on the constructor. It also can lead to “conveyance objects” that begin to take on a life of their own beyond their original intent of simply acting as a “ferry” to get a bunch of data fields in and out of an object (or form).
Property injection is probably the most versatile as it gives you the most insulation from implmentation changes while offering hooks to get and set inidividual properties on the form. If the getters/setters read/write directly from/to the edit fields on the form, then this can be very handy. It’s major drawback is that the compiler can’t warn you if you’re missing any. But there’s a way to get around this at run-time. Stay tuned.
Backdoor injection should be avoided at all costs, as it will invariably increase maintenance costs downstream because of the tight coupling between the form and every code unit that uses it.
In the next post, we’ll look at another form of injection that isn’t used very often, but can be quite powerful. I’m not sure it has a name, but it’s where you set up call-backs and lambda functions to request data be handed to the form.
For a more in-depth discussion of dependency injection, here’s a great article by Mark Seeman:
For more details about this particular aspect of dependency injection, and how to handle long argument lists in constructors, here’s another post of Mark’s to look at:
(FWIW, Mark Seeman literally “wrote the book” on “Property Injection in .Net”. It’s an excellenet book and a great tutorial and reference guide regardless of what language you’re working in. The concepts are the same, but the implementation details will vary slightly between languages. I highly recommend it.)