Basics
Without the concept of dependency injection, a consumer who needs a particular service in order to accomplish a certain task would be responsible for handling the life-cycle (instantiating, opening and closing streams, disposing, etc.) of that service. Using the concept of dependency injection, however, the life-cycle of a service is handled by a dependency provider (typically a container) rather than the consumer. The consumer would thus only need a reference to an implementation of the service that it needed in order to accomplish the necessary task.
Such a pattern involves at least three elements: a dependent, its dependencies and an injector (sometimes referred to as a provider or container). The dependent is a consumer that needs to accomplish a task in a computer program. In order to do so, it needs the help of various services (the dependencies) that execute certain sub-tasks. The provider is the component that is able to compose the dependent and its dependencies so that they are ready to be used, while also managing these objects' life-cycles. This injector may be implemented, for example, as a service locator, an abstract factory, a factory method or a more complex abstraction such as a framework.
The following is an example. A car (the consumer) depends upon an engine (the dependency) in order to move. The car's engine is made by an automaker (the dependency provider). The car does not know how to install an engine into itself, but it needs an engine in order to move. The automaker installs an engine into the car and the car utilizes the engine to move.
When the concept of dependency injection is used, it decouples high-level modules from low-level services. The result is called the dependency inversion principle.
[edit] Code illustration using Java
Using the car/engine example above mentioned, the following Java examples show how coupled dependencies (manually-injected dependencies) and framework-injected dependencies are typically staged.
public interface Engine {
public float getEngineRPM();
public void setFuelConsumptionRate(float flowInGallonsPerMinute);
}
public interface Car {
public float getSpeedInMPH();
public void setPedalPressure(float pedalPressureInPounds);
}
[edit] Highly coupled dependency
The following shows a common arrangement with no dependency injection applied:
public class DefaultEngineImpl implements Engine {
private float engineRPM = 0;
public float getEngineRPM() {
return engineRPM;
}
public void setFuelConsumptionRate(float flowInGallonsPerMinute) {
engineRPM = ...;
}
}
public class DefaultCarImpl implements Car {
private Engine engine = new DefaultEngineImpl();
public float getSpeedInMPH() {
return engine.getEngineRPM() * ...;
}
public void setPedalPressure(float pedalPressureInPounds) {
engine.setFuelConsumptionRate(...);
}
}
public class MyApplication {
public static void main(String[] args) {
Car car = new DefaultCarImpl();
car.setPedalPressure(5);
float speed = car.getSpeedInMPH();
}
}
In the above example, the Car class creates an instance of an Engine implementation in order to perform operations on the car. Hence, it is considered highly coupled because it couples a car directly with a particular engine implementation.
In cases where the DefaultEngineImpl dependency is managed outside of the scope of the Car class, the Car class must not instantiate the DefaultEngineImpl dependency. Instead, that dependency is injected externally.
[edit] Manually-injected dependency
Refactoring the above example to use manual injection:
public class DefaultCarImpl implements Car {
private Engine engine;
public DefaultCarImpl(Engine engineImpl) {
engine = engineImpl;
}
public float getSpeedInMPH() {
return engine.getEngineRPM() * ...;
}
public void setPedalPressure(float pedalPressureInPounds) {
engine.setFuelConsumptionRate(...);
}
}
public class CarFactory {
public static Car buildCar() {
return new DefaultCarImpl(new DefaultEngineImpl());
}
}
public class MyApplication {
public static void main(String[] args) {
Car car = CarFactory.buildCar();
car.setPedalPressure(5);
float speed = car.getSpeedInMPH();
}
}
In the example above, the CarFactory class assembles a car and an engine together by injecting a particular engine implementation into a car. This moves the dependency management from the Car class into the CarFactory class. However, this still may not be enough abstraction for some applications.
[edit] Framework-managed dependency injection
There are several frameworks available that automate dependency management by delegating the management of dependencies. Typically, this is accomplished by a Container using XML or "meta data" definitions. Refactoring the above example to use an external XML-definition framework:
<service-point id="CarBuilderService">
<invoke-factory>
<construct class="Car">
<service>DefaultCarImpl</service>
<service>DefaultEngineImpl</service>
</construct>
</invoke-factory>
</service-point>
/** Implementation not shown **/
public class MyApplication {
public static void main(String[] args) {
Service service = (Service)DependencyManager.get("CarBuilderService");
Car car = (Car)service.getService(Car.class);
car.setPedalPressure(5);
float speed = car.getSpeedInMPH();
}
}
In the above example, a dependency injection service is used to retrieve a CarBuilderService service. When a Car is requested, the service returns an appropriate implementation for both the car and its engine.
As there are many ways to implement dependency injection, only a small subset of examples is shown herein. Dependencies can be registered, bound, located, externally injected, etc., by many different means. Hence, moving dependency management from one module to another can be accomplished in a plethora of ways. However, there should exist a definite reason for moving a dependency away from the object that needs it because doing so can complicate the code hierarchy to such an extent that its usage appears to be "magical". For example, suppose a Web container is initialized with an association between two dependencies and that a user who wants to use one of those dependencies is unaware of the association. The user would thus not be able to detect any linkage between those dependencies and hence might cause drastic problems by using one of those dependencies .
[edit] Benefits and drawbacks
One benefit of using the dependency injection approach is the reduction of boilerplate code in the application objects since all work to initialize or setup dependencies is handled by a provider component.[2]
Another benefit is that it offers configuration flexibility because alternative implementations of a given service can be used without recompiling code. This is useful in unit testing because it is easy to inject a fake implementation of a service into the object being tested by changing the configuration file.
One drawback is that excessive or inappropriate use of dependency injection can make applications more complicated, harder to understand and more difficult to modify. Code that uses dependency injection can seem magical to some developers, since instantiation and initialization of objects is handled completely separately from the code that uses it. This separation can also result in problems that are hard to diagnose. Additionally, some dependency injection frameworks maintain verbose configuration files, requiring that a developer understand the configuration as well as the code in order to change it.
Another drawback is that some IDEs might not be able to accurately analyze or refactor code when configuration is "invisible" to it. Some IDEs mitigate this problem by providing explicit support for various frameworks. Additionally, some frameworks provide configuration using the programming language itself, allowing refactoring directly. Other frameworks, such as the Grok web framework, introspect the code and use convention over configuration as an alternative form of deducing configuration information. For example, if a Model and View class were in the same module, then an instance of the View will be created with the appropriate Model instance passed into the constructor.
[edit] Criticisms
A criticism of dependency injection is that it is simply a re-branding of existing object-oriented design concepts. The examples typically cited (including the one above) simply show how to fix bad code, not a new programming paradigm. Offering constructors and/or setter methods that take interfaces, relieving the implementing class from having to choose an implementation, is an idea that was rooted in object-oriented programming long before Martin Fowler's article or the creation of any of the recent frameworks that champion it.
[edit] Types
Fowler identifies three ways in which an object can get a reference to an external module, according to the pattern used to provide the dependency:[3]
Type 1 or interface injection, in which the exported module provides an interface that its users must implement in order to get the dependencies at runtime.
Type 2 or setter injection, in which the dependent module exposes a setter method that the framework uses to inject the dependency.
Type 3 or constructor injection, in which the dependencies are provided through the class constructor.
It is possible for other frameworks to have other types of injection, beyond those presented above.[4]