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.
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.|
At this point, I am very proud of my Scanner class and show it to my fellow programmers. They are somewhat impressed, but they (being accomplished software developers themselves) suggest all sorts of extensions to the class. Can you add a parameter to specify the minimum and maximum size of the file? How about a pair of parameters to specify the date range when the file was created? Ditto last written? Ditto last accessed? Can you add parameters to specify more than one extension? How about the ability to specify a regular expression for the file name test? And on it goes. I could, of course, write code for each of these possibilities, but it is clear that this would be a never-ending task. Each time that I got one possibility working, someone would suggest yet another possibility. My nice simple Scanner class would be “junked up” in no time.
One of the goals of writing a class is to get it “done.” That is, I want to write the code, write the unit tests, write the documentation, create the deployment logic, and so on, and then put all of this “on the shelf” where I can re-use it. The principle that I’m interested in here is called the “open closed” principle. I want the Scanner class to be open for extension but closed for modification. In other words, I want to create a Scanner class that implements a certain amount of functionality (that is done) that I can extend by specifying parameters or though one of the techniques that I’ll cover in this article. I do not want to have to keep going back to working (and tested) code to make modifications. To achieve this state of near-nirvana is difficult but not impossible. One way to achieve this state of ultimate tranquility is to focus on the relevant essentials and ignore everything else.
If the Scanner class squints its eyes (to abstract out the irrelevant details), it becomes obvious that the only thing that is relevant to the Scanner (for all of the above possible extensions) is whether the individual file “is of interest or not.” The Scanner class really does not care about how this determination is made or even where it is made. All that you have to do is to define a “predicate” function that accepts information about the file and returns a Boolean “true” or “false.” You could make this an overridable function and create derived child classes that provide the specific implementation of the predicate function, or you could create a delegate method signature for the predicate function and pass in an appropriate delegate to handle the relevance test. I’ll show you how to use this second approach. You can see the next “Zen koan” to mark the journey in Listing 3.
The Scanner class walks the file hierarchy, creating a FileInfo class instance for each file that it finds. It then passes each FileInfo instance to the predicate function. The predicate function determines whether the file is of interest. You’ll see a a number of significant changes in this version of the program. Because added a number of different delegates, I decided to create the GUI component in Figure 1.
|Figure 1. Scanner Class GUI: This simple GUI lets users input an initial directory, a log file, and specify file extension, size, and date filters.|
This GUI presents three different options (along with modifying parameters) for the predicate function delegate. Because the GUI provides way to display the results immediately, I changed the capture logic from writing to a specified log file to raising an event that the caller can capture. In this example, the GUI displays the data on a scrollable textbox. Finally, I added the delegate logic to test the relevance of the file.
Revisiting the function list, at this point, the software does the following:
- 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).
- Navigates the file hierarchy.
- Invokes a delegate to test for relevancy (loss of control to an external mechanism).
- Raises an event for each relevant file (loss of control to an external mechanism).
Two interesting things have happened here. First, I injected the logic for the relevancy test into the Scanner class. Second, I’ve removed the logic from the Scanner class that handles each relevant file; the code in Listing 3 raises an event with the pathname of each relevant file, but has no idea of what will be done, if anything, with that value. The value could be written to the console, written to a log file, displayed on a GUI control, and so on. The Scanner class has now been pared to its essence: to navigate the file hierarchy; test each file for relevancy, and raise an event for the files that pass the relevancy test. Everything else has been removed. Paradoxically, the class does less but has become considerably more useful. However, I’m still passing in a file extension, which isn’t required, but is typical of programs that evolve: This is a vestigial parameter that has not quite withered away.
May You Live in Interesting Times
Now assume some time has passed?in fact, a lot of time has passed. While the vision and implementation of the Scanner class might have seemed close to the sought-after state of perfection, the Scanner class and its supporting software have continued to evolve. (I won’t continue the Zen references from this point forward because now we’re going to deal with the more tangible realities of developing software in a business environment.) In the time since the enhancements you saw previously in this article, your development team has modified the Scanner class to respond to requests for:
- The ability to capture and process the data in the relevant files.
- The ability to rollup processing after all of the individual files have been processed.
- The ability to output the captured and processed data in various ways.
Vicki Vice President found about this software and has requested that you build a revenue reporting system for the company’s ancient Rocks-a-Lot order entry system. This system has been in place for almost 30 years, and is the division’s main system. As each order comes in, the Rocks-a-Lot system creates a file with the details of the order and saves the file within the directory structure, using a naming scheme where the directory path specifies the company and reporting period. The order fulfillment system scans the directory structure and ships the product. At this point, your inner software developer is screaming that there are an infinite number of opportunities for improving this system. Be patient, Grasshopper. The careers of several vice presidents have crashed upon the shores of the Rocks-a-Lot system in futile efforts to upgrade or replace the system. It has become the dreaded “third rail” of IT systems within the company. Nobody?not even the ultra-capable Miss Vicki?wants to take on this particular monster. The only safe thing to do is to build out from the original system.
The Era of Interfaces
As a part of the Rocks-a-Lot Reporting Extension, your team has implemented two major changes:
First, the team changed the language for creating extensions from C# to Visual Basic. The company determined that they have more Visual Basic software development resources available, (plus Vicki Vice President had coded in classic Visual Basic “a long time ago”). This is not a problem; the development principles that I’ll show you apply to any language.
Second, the scanner class has shifted from using delegates to using flexible interfaces that support multiple methods. I’ll walk you through a brief history of the key interfaces in the order that the team introduced the interface into the program:
The first interface is essentially the delegate predicate function from above packaged as an interface. The GUI form injects the filter interface as a constructor parameter when it instantiated the Scanner class:
Public Sub New(ByVal filter As IFilterFile)
The next interface (IProcessFile), handles processing for files that pass the relevancy filter:
Public Sub New(ByRef filter As IFilterFile, _ ByVal processor As IProcessFile)
This next interface handles the post processing of all files that passed the relevancy filter (IPostProcessFile):
Public Sub New(ByVal filter As IFilterFile,_ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile)
As you can see here, the next interface (IStorageSystem) abstracts the physical file system (introduced to allow for unit testing and to allow the scanner to process XML files that hold summary data):
Public Sub New(ByVal storage As IStorageSystem, _ ByVal filter As IFilterFile, _ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile)
Now it’s time to handle the file contents. This next interface splits the processing of a relevant file into two interfaces: one that extracts/builds the contents of the relevant file and a second to process those contents:
Public Sub New(ByVal storage As IStorageSystem, _ ByVal filter As IFilterFile, _ ByVal contentFilter As IFilterContents, _ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile)
Next, here’s an interface that formats the contents of the processed data:
Public Sub New(ByVal storage As IStorageSystem, _ ByVal filter As IFilterFile, _ ByVal contentFilter As IFilterContents, _ ByVal processor As IProcessFile, _ ByVal postProcessor As IPostProcessFile, _ ByVal formatter As IFormatOutput)
Well, you can probably see where this is going. The truth is that after I taught the Visual Basic programmers about interfaces and dependency injection, they went a little (actually, a lot) overboard. If it moved, there was an interface defined and at least one (but usually just one) class that implemented the interface. The addition of “Moderation in All Things” posters in the development workspace came too late to do any real good.
Some developers argue that the use of interfaces can degrade performance (true, but typically not particularly relevant), but the real issue with going overboard on interfaces is one of maintenance. The team got to a point in the development of the Scanner class where they had a constructor with nine different parameters, each representing the implementation of a particular interface needed by the Scanner class. The team’s noble Scanning class had become the poster child for pincushions around the world: injection after injection. The biggest problem was that the responsibility for passing the interface implementations to the Scanner class lay with the GUI. When users clicked on the “Capture Rocks-a-Lot Reporting Data” (the actual unedited caption for the button), the GUI would appear and configure each interface implementations in preparation for passing them as parameter values in the Scanner constructor. Remember my earlier description of the “open closed” principle? Suppose you want to “interview” the Scanner class. The interview might go something like this:
“I understand the need to be a team player. If something comes up that needs to be done and I can be of help, then I stand ready to take on the task. But I have to tell you, every time this Scanner class needs a new interface, the developers come trooping through my front room with their muddy boots. I have to have the carpet cleaned each time that happens. Mind you, I don’t begrudge Scanner any of the interfaces. Lord knows, Scanner does yeoman duty. Without Scanner we would all be out of jobs, but my question to you is, why do I have to be involved? My GUI does not change, but here I am instantiating and configuring things that I really have no knowledge of. I wake up some mornings with the shakes: what would happen if I mess up? There just has to be a better way!”
You want to be able to finish the work on the GUI for once and for all. In an ideal world, changes to the Scanner class that do not absolutely require changes to the GUI logic should never cause changes to the GUI class. The problem is that you’re injecting the dependencies through the Scanner’s constructor, and the GUI is responsible for the instantiation of the Scanner class.
The Rise (and Fall) of the Factory Method
One way out of this problem is to refactor the GUI logic so that Scanner instantiation and configuration is done somewhere else. The home for this logic would be called a factory method. I am introducing a level of indirection here. The logic in the GUI class would call an intermediate “broker” or “factory” method. The factory method would instantiate the Scanner class, configure it as needed, and return it to the GUI class. The GUI class would invoke the Scanner Process method with the path to the initial file directory. All of the “just one more interface” changes would happen in the factory method rather than in the GUI class. No more muddy boot prints on the GUI carpet.
This arrangement is clearly better than having the GUI class handle the instantiation. You can nail the GUI class down and say with some truth that it is truly done. The GUI class has a single responsibility, as does the factory method. However, you’re not yet in a state of nirvana. The factory method has too much knowledge of the innards of the Scanner class and of the classes that the Scanner class uses. I’ve focused on the Scanner class and how to inject dependencies into that class. It’s time to extend that awareness to the cast of dependent classes. It is quite possible that each of the dependent classes also have dependencies that need injection treatment. Rinse and repeat for as many levels of dependencies that your program has. In a real-world system, you could have several dozen such dependencies, spread over a dozen different assemblies/components.
You can make it even more complicated. Suppose that you have three different formatting classes, F1, F2, and F3, each of which implements the IFormatOutput interface. Suppose that F1 is self-contained and has no dependencies, that F2 needs dependencies Q1 and Q2, and F3 needs dependencies Z1, Z2, and Z3. If this is not complicated enough for you, suppose that each of the classes Q1, Q2, Z1, Z2, and Z3 have different dependencies. Trust me on this! I can keep going until your head explodes. The factory method has become way too complicated. The GUI class is content, but now the factory method calls out, “There must be a better way!”
A Primitive Broker
There is a better way! Let’s take a look at what the factory method (or the GUI class before it) is trying to do. It is instantiating the classes that implement the interfaces and it is injecting those interfaces into the classes that have dependencies on these interfaces. Suppose that you split those responsibilities apart: one for instantiation and a second for injection.
Let’s look at the injection responsibility first. Each class that has a dependency knows that it has a dependency. The overall goal is not to pretend that the dependency does not exist but to keep the class from knowing the details of how the dependency is satisfied. What if the class with the dependency could just “ask” for an instance of a class that implements the interface?and just get it without having to know how it was instantiated? I put the “ask” action in quotes to suggest a metaphorical inquiry rather than a necessarily explicit request. For example, the constructor parameters described above could easily be interpreted as requests for the various interfaces. The class could mark certain setter methods to indicate that the setter represented a dependency. Finally, the class could make an explicit request. Each approach has both positive and negative aspects, but all serve to decouple a class with dependencies from the classes that might be used to satisfy those dependencies.
Let’s assume that each class makes an explicit request for each dependent interface to a “broker” class that has the responsibility of instantiating objects that implement the specified interface. A very primitive broker might consist of a big case/switch statement that instantiates a hard-coded class for each type of interface. This is admittedly crude but still it is effective. A little less primitive broker might use reflection to find the parameters in the constructor or the setters of the newly instantiated class that are marked as dependencies and recursively call itself to satisfy these dependencies. You could write this in roughly 100 lines of code and debug in a matter of hours.
While not a perfect solution, this does go a long way toward simplifying the logic. Each class is responsible for injecting its own direct dependencies. None of the classes needs to know how to instantiate its dependencies. The broker class needs to know how to instantiate classes to satisfy the various interfaces but does not need to know who might need the dependencies. The broker certainly does not need to know about the hierarchy of dependencies.
Remember that this is a technique, not a technology. There is nothing to download, nothing to install, and nothing to add to the references. All that you have to do is to change the way that you think about dependencies. Oh yes, there is the business about disciplining the development process to prevent direct instantiation of dependent classes that you might want to switch out sometime in the future. Simple!
A Clever Broker
Even though you have decoupled the Scanner class from the means of satisfying its dependencies, there are other problems to solve. The primitive broker is still hard-wired. The application is still strongly coupled to the broker. This becomes a problem if you want to be able to instantiate different implementations of the various interfaces depending upon the execution context.
In addition to production, one of the most obvious contexts is unit testing. In unit testing you want to isolate the logic of each class so that you can test the functionality of that class without all of the baggage of the dependent classes. Since the Scanner class is dependent upon interfaces (rather than concrete classes), the unit test context could satisfy these dependencies with “fake” versions of the interfaces. The unit test context could do that if the Scanner class was not tightly coupled to a specific broker.
So here you want to insert “yet another layer of indirection” that will allow you to further decouple the Scanner class from the broker. The first thing that you have to do is to introduce another interface:
Public Interface IResolveDependencies Sub RegisterImplementationOf(Of T)( _ ByVal component As T) Function GetImplementationOf(Of T)() As TEnd Interface
This interface defines the key functions of the broker. The RegisterImplementationOf method allows the caller to supply an instance of a class that implements a specified interface. The GetImplementationOf method allows the caller to retrieve an instance of a class that implements the specified interface.
Next, you must create a “container” to hold the current broker. Different environments would store different versions of the broker in this container. Each class that wanted to retrieve one of its dependencies would ask the container to resolve the dependency by calling the GetImplementationOf method of the container. The container, in turn, would pass the request on to the inner broker.
Public Module DependencyResolver Private _resolver As IResolveDependencies Public Sub RegisterResolver(ByVal _ resolver As IResolveDependencies) _resolver = resolver End Sub Public Sub RegisterImplementationOf(Of T)( _ ByVal component As T) _resolver.RegisterImplementationOf( _ Of T)(component) End Sub Public Function GetImplementationOf( _ Of T)() As T Return _resolver.GetImplementationOf( Of T)() End FunctionEnd Module
Each environment (e.g., production, Q/A, and Unit Test) would call the RegisterResolver method to supply the particular broker that would handle that environment.
|Author’s Note: I make no claim to having invented this technique. James Kovacs wrote a very nice MSDN Magazine article on how to use this technique.|
A Sophisticated Broker
You’re getting closer to perfection, but there is still room for improvement. So far, you’ve hard-coded the logic to instantiate the classes that will satisfy the interface dependencies. That is, you are specifying the interface implementations at compile time. Now you’ll want to introduce some configuration options that would specify the classes to instantiate for each interface at run time. The Scanner class would “make a request” to the broker for an implementation for each dependent interface. The broker would read the configuration data, locate the linkage between the desired interface and the class that implemented that interface, and instantiate the specified class. At this point, you’ve achieved near-perfection with respect to decoupling the Scanner class from its dependencies.
This perfection comes at a cost, however. The logic to manage the configuration data and to instantiate the various classes can be challenging. You can easily generalize this logic to apply to any application, so you have a situation where you could create a standard component to hold this logic. Such components are typically called “inversion of control (IOC) containers.” There are a number of these IOC containers available for use in your applications. Although my intent is not to delve into the details of each of these IOC containers, I will list a few that you might want to consider:
Usefulness and Loss of Control
Almost every class that is of some interest is dependent on external elements to accomplish its work. The usefulness of the class is inversely proportional to how concrete the specifications of these external dependencies are. The less control that the class has over its environment, the more useful the class can be in a variety of environments. One aspect of good design is to reduce?or even eliminate?the control that each class needs to have. This article covered a variety of different control extraction techniques, from introducing parameter values, to using delegates, to supporting pairs of interfaces and implementations of those interfaces. Each technique is useful for allowing classes to specify their needs in the most abstract way possible, and each technique is a valid response to the dependency issues in various situations.
After identifying a dependency, the next issue is how to satisfy the dependency. When using parameters and delegates, responsibility for satisfying the dependencies lies with the logic that invokes the class. When using interfaces, the invoking logic can satisfy the responsibility as well, but as the density of interface usage increases, you must look for other ways to reduce the “conceptual congestion.” Specifying dependencies in terms of interfaces opens up the possibility of using a broker mechanism to resolve the dependencies. The broker can range from a hard-coded primitive broker to a configuration-driven sophisticated broker shareable across multiple applications.
Once again, this article is not intended to lead you to use a single approach, but rather to expose the underlying principles so that you can choose the approach that makes the most sense in your particular situation.