n the surface, this article is about the techniques of dependency injection and inversion of control. Underneath, however, the article is intended to get you to think about the questions of why and when you might want to use these two closely related techniques, as well a series of similar evolutionary techniques that lead up to full-blown dependency injection. The initial code samples are admittedly (and deliberately) simple; I do not want the code's content to obscure its intent.
What's Inversion of Control?
Inversion of control is, at its heart, a software design approach (that may or may not be supported by external libraries).
The goal of the "dependency design" effort is to replace each reference to a concrete class with a reference to an interface (or abstract class), such that the class does not care which concrete object implements the interface.
There are several ways that the application can resolve the interfaces upon which the class depends: through the constructor for the class, through "setter" properties, through factory methods, and through an inversion of control container.
With that in mind, I'll use the code examples in this article to take you on a journey, starting with some very simple code that evolves through a number of revisions, not unlike the revisions that happen to real programs. The programs will be of limited use (unless you live in a particularly impoverished development shop). What is more important is the "color commentary" that accompanies each of these "Zen koan" code samples. (The Zen Master teaches the student by posing "koans" to expand a student's thinking. A koan is essentially an open-ended riddle or story upon which the student meditates and, in time, achieves wisdom.)
With some luck, all of this will lead you to understand the Zen of Inversion of Control.
Some time ago, I was looking for a file. I had created the file a year before to support some decisions about employment benefits, made the decisions, and moved on. I had not touched the file for a year. With storage as cheap as it is, I knew that I had kept the file but had only a vague idea of where I might have archived it. I could not remember the name of the file. I knew that it was a spreadsheet, but I was not sure if it had an .xls or .xlsx extension. I decided to write a simple program to look for the file in the most likely places. Once I found it, I could update it and make my decisions about employment benefits for the next year.
The main procedure instantiates a Scanner class that walks the file hierarchy, looking for the file in question. The Scanner class is the protagonist in this story: I'll show you how this character "grows" as the tale evolves. Listing 1 shows the first version of the Scanner class.
|You can build your own simple inversion of the control container or use one of the many libraries that provide inversion of control functionality.|
Here's the first round of color commentary. The program in Listing 1 does use recursion to walk the file hierarchy, but other than that it is very simple. The functionality of this software, which serves as the first Zen koan to focus our minds, is to:
- Specify the path to the initial directory.
- Specify the extension to test for.
- Specify the path to the log file.
- Navigate the file hierarchy.
- Test each file for relevancy.
- Write the path of each relevant file (along with its containing directory) to the log file.
As this story progresses, I'll return to this list of functions to see what changes. One important aspect of this first version is that the program is totally in control of what it does. If I give you a copy of this program, about the only decisions that you (through the main program) can make are to run it or not run it, and what computer to run it on. All other aspects of control are lodged in the Scanner class. The consequence of this is that the Scanner class is useful for a very specific and narrow purpose, but outside of that area, it is not at all interesting.
As a software developer, my first response is to fix these deficiencies in the Scanner class by adding parameters that specify the starting directory for the scan, the file extension to search for, and the location of the log file that holds the full pathnames for the files that the scanning process finds. Listing 2 shows the next "Zen koan" to mark this part of the journey.
The functionality in Listing 2 is not much different from that in Listing 1, but because it introduces external inputs, I have added some input validation (this might be a "toy" program but professional practices are professional practices). You might not typically think of parameters as inversion of control, but the Scanner class has, by accepting these parameters, given up some aspects of control to an external mechanism. That is, the program is now dependent upon the values of parameters injected into the program from an external source. This altered version:
- Accepts a path to the initial directory (loss of control to an external mechanism).
- Accepts an extension to test for (loss of control to an external mechanism).
- Accepts a path to a log file (loss of control to an external mechanism).
- Navigates the file hierarchy.
- Tests each file for relevancy.
- Writes the path of each relevant file (along with its containing directory) to the log file.
The main program that instantiates and invokes the Scanner class now has much more control over what the Scanner program does. If the main program exposes the options for the Scanner class as program parameters, you and I, as users of this program, get more control. Because the Scanner class is more flexible, it has become more popular (albeit at the cost of losing some of the control over its functionality). I, and others, could use this program to find all sorts of files. (And, yes, you can find utilities that do this much better but these utilities do not illustrate my points nearly as well.)
|Editor's Note: This article was first published in the March/April, 2009 issue of CoDe Magazine, and is reprinted here by permission.|