|
By Timothy Fisher
If you're active in the Java community, you've probably come across the term "dependency injection." This is a term that’s used more than it's defined, and has left a lot of developers without a clear understanding of what dependency injection is, why it's useful, and how to implement it. This article will address the what, why, and how of dependency injection.
Dependency injection deals with the concept of how we wire different elements of a program together. With the current push towards componentization of our applications this is an important concept to understand in order to build testable, and highly scalable applications.
Dependency injection is also often referred to as "Inversion of Control." The term dependency injection turns out to be a more exact description of the usage of inversion of control for the problem context we are interested in. Before even delving into what dependency injection is, let's state the goals and benefits of dependency injection. Through proper application, dependency injection can deliver a highly decoupled, highly reusable, very unit-test friendly, highly scalable, and very dynamically pluggable code base.
Although, you may not be aware of it, most developers have used the Inversion of Control pattern. A place where most of us have seen it is in the development of user interfaces. Remember back to the days when the user interface consisted of your program writing out prompts to the command line and waiting for user input. In this model, your application was in complete control. But, as we were pulled into developing user interfaces for the Windows world, suddenly things got more complex. Here the typical model became, register your event listeners with the various UI objects, and the UI will call you to process events. This is the Inversion of Control pattern. Your application is no longer in control of the main processing loop. Instead, you have given that control to the UI framework. So, Inversion of Control seems to be a pattern whereby you abdicate process control to another resource, outside of your programmatic control.
So, given that, now the question is, how does Inversion of Control relate to Dependency Injection? Well, a few years ago a few open source development teams began developing what they referred to as light-weight containers. Inversion of Control was touted as a primary feature of these new containers. This was the Inversion of Control pattern applied in a different area though. To illustrate this use of IOC, let me divert the conversation a bit to discuss a common development problem.
For this example, let's assume we wanted to develop a simple program that would allow us to have a candidate for a given election selected for us automatically, based on compatibility of our values with those of the candidate. A simple object model of this program might look like figure 1.0.
Figure 1.0
In this model we have a CandidateChooser class which uses a CandidateEvaluator class to evaluate the candidates for us. So, we have a dependency between the CandidateChooser and the CandidateEvaluator class. There are 2 reasons why this dependency may be a bad thing, the first being that it makes testing more difficult, and the second being that it limits the re-usability of our CandidateChooser class.
So, instead of tying ourselves to a particular implementation of the CandidateEvaluator, let's create an Evaluator interface, and make our CandidateChooser dependent on that. Now we can plug-in the particular Evaluator implementation that we want. So far, so good, but, we still have a problem. There still must be some place in our code where we instantiate a concrete implementation of the Evaluator interface. So, at this spot, we again have the dependency on the concrete implementation that we were seeking to remove.
Figure 2
Now, we've set the stage to get back to our discussion of IOC. We solve the dependency problem we ran into in the above scenario by applying Inversion of Control. We let an external container decide the concrete implementation of an interface to instantiate. We do not make that decision in our application code. This type of Inversion of Control is implemented through a technique we call dependency injection. Aha, we now see a relationship between IOC and dependency injection. Figure 3 shows our example with the addition of an external container performing the dependency injection.
Figure 3
When you want to get an instance of an object that is dependent on a container managed object, you ask the container to provide the instance. The container knows which concrete implementation of the dependent object to use and returns you an object with the appropriate dependency.
The next question we have to deal with is how the container knows which specific implementation of a dependent object an object should use. This step can be thought of as wiring all the objects in your application together. This requires a mapping of dependencies and implementations. This mapping is commonly either read from an XML file, or implemented in a separate class.
Dependency Injection and Unit Testing
Dependency injection brings improved testability as another of its benefits. Through the use of dependency injection, objects are no longer coupled directly to resource dependent objects, or heavy-weight container objects. Object dependencies can easily be mocked up and an object can be unit tested in isolation of other concerns.
Dependency Injection and Encapsulation
If you apply the dependency injection/IOC pattern throughout your code, you end up with a collection of objects, each exposing their dependencies through constructors or setter methods. Managing dependencies for this collection of objects is the lightweight container whose job it is to fulfill all the dependencies, thus creating a complete set of objects dynamically.
The fact that dependency injection requires all of your objects to expose their dependencies is sometimes pointed out as a criticism of this pattern. The thought is that this violates the fundamental object oriented principle of encapsulation. Strictly applying the principle of encapsulation would dictate that each object should know and be able to create their own dependencies, without having to expose those to other objects. What this really highlights is the fact that encapsulation should not be mandated so strictly. Strict adherence to this concept can cause all kinds of ugly design and coding workarounds to achieve desired functionality. There has to come a point when you recognize the limitations of any principle, and are willing to relax adherence to that principle when it makes sense and the net improvement in design is a positive one. Most people familiar with dependency injection do indeed consider it a net positive improvement in design and architecture. It actually applies encapsulation in a slightly different paradigm. It allows objects to focus on their intended functionality, without having to worry about the details of all the objects it may be using to implement that functionality. This can be thought of as functional encapsulation.
Dependency Injection and Factories
So, at this point, I'm sure that some of the readers are thinking to themselves. This all sounds pretty nice, but why not just use the factory pattern and factory objects. Isn't that easier, and it eliminates the need for a container all together. Very good question, I think that dependency injection can be thought of as a further step on the design evolutionary path preceded by factory objects. First, let's remember one of the goals here is to eliminate hard-coded object dependencies. So, if we are using a factory to create an instance of our dependency, we still have a hard-coded dependency in the factory, unless we externalize that into a configuration file. To take all the dependencies out of our main code-base, we end up with tons of factory objects.
Constructor Injection
Now that you have a basic understanding of dependency injection, let's discuss two ways it is commonly implemented. The first way is called constructor injection. In this method, dependencies are passed as parameters to an object's constructor at object creation time.
Setter Injection
The other popular form of dependency injection is called setter injection. Using setter injection, the dependencies are set using Java bean style setter methods on the object.
So what's better, constructor or setter injection? There are vocal camps on both sides of that question. For example, the team that developed the Spring container favors setter injection, while the team that developed the PicoContainer project favors constructor injection. Ultimately, I don't believe there is a universal best method here. You need to evaluate the two methods and decide which is preferred for your project.
Dependency Injection Containers
There are a growing number of frameworks that make dependency injection easier for the developer. These frameworks are most often referred to as light-weight containers. Currently, the best choices for a light-weight container are in the open source community. The two most common examples are Spring, and PicoContainer, both open source products. Both of these products have very good reputations, strong advocates, and large and very supportive communities. Spring seems to be gathering more and more steam and capturing more mind-share every day.
These containers allow you to remove hard-coded dependencies from your source code. Instead of in the source code, these dependencies are typically specified in an XML file. At runtime, the container then dynamically builds these objects and creates all the required dependencies.
While dependency injection is a primary feature of Spring, Spring also includes other framework components which are worth looking at and evaluating on their own.
Summary
So, having read through this article, I hope that you now feel better able to understand what Dependency Injection and lightweight containers bring to the table. This is such a hot topic currently that your certainly better off having that knowledge.
Until next time,
Timothy Fisher
Here's the usual page people link to when talking about this same
stuff:
http://martinfowler.com/articles/injection.html
Excellent Article. I have used this with many success in the architecture
and implementation of applications.
Stefan Sookraj
Good short article. Now I know exactly how to explain difference between
Dependency Injection and Inversion of Control...
Sandy Smith
Thanks for such a clear and concise explanation.
What if we have components having their own dependencies (in terms of the
libraries they include). Let there be a component A on which another
component B depends. Will the container pull the libraries included in A
and place them in B?