Dependency injection (
DI) in
computer programmingComputer programming is the process of writing, testing, debugging/troubleshooting, and maintaining the source code of computer programs. This source code is written in a programming language. The code may be a modification of an existing source or something completely new...
refers to the process of supplying an
external dependencyIn computer science, coupling or dependency is the degree to which each program module relies on each one of the other modules.Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa...
to a software component. It is a specific form of
inversion of controlInversion of control, or IoC, is an abstract principle describing an aspect of some software architecture designs in which the flow of control of a system is inverted in comparison to procedural programming....
where the concern being inverted is the process of obtaining the needed dependency. The term was first coined by
Martin FowlerMartin Fowler is an author and international speaker on software development, specializing in object-oriented analysis and design, UML, patterns, and agile software development methodologies, including extreme programming....
to describe the mechanism more clearly.
Basics
Conventionally, if an object, in order to accomplish a certain task, needs a particular service, it will also be responsible for instantiating and disposing of (removing from memory, closing streams, etc.) the service, therefore making it more complex and hard to maintain. Ideally, this object would not need to manage its service's life cycle but just have a reference to an
implementationA programming language implementation is a system for executing programs written in a programming language.There are two general approaches to programming language implementation:...
of the service and invoke its relevant behaviors. Dependency injection is a
design patternA design pattern in architecture and computer science is a formal way of documenting a solution to a design problem in a particular field of expertise. The idea was introduced by the architect Christopher Alexander in the field of architecture and has been adapted for various other disciplines,...
that can be applied to provide an object with its dependencies and move the code related to the service life cycle to a more appropriate place.
Such a pattern involves at least three elements: a
dependant, its
dependencies and an
injector (sometimes referred to as a
provider or
container). The dependant is an object that is expected to accomplish a relevant task in a
computer programComputer programs are instructions for a computer. A computer requires programs to function, typically executing the program's instructions in a central processor. The program has an executable form that the computer can use directly to execute the instructions...
. In order to do so, it needs the help of other objects (the dependencies) that provide specialized services. The provider is the component that is able to
composeIn computer science, object composition is a way to combine simple objects or data types into more complex ones...
the dependant and its dependencies so they are ready to be used, while also managing other aspects of these objects' life cycle, such as when and how they are instantiated (cached or not, for example). This provider may be implemented, for example, as a
service locatorThe service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer...
, an abstract factory, a factory method or a more complex abstraction such as a
frameworkA software framework, in computer programming, is an abstraction in which common code providing generic functionality can be selectively overridden or specialized by user code providing specific functionality...
.
As a simple example of the above explanation, we can think of a car as the dependant, the engine as the dependency and a car factory as a provider. A car does not know how to install an engine on itself, but it needs an engine to run. The installation of an engine on a car is a car factory responsibility.
When the dependency injection technique is used to decouple high-level modules from low-level services, the resulting design guideline is called the
dependency inversion principleIn object-oriented programming, the dependency inversion principle refers to a specific form of decoupling where conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are inverted for the purpose of rendering high-level modules...
.
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.
For these examples, assume the
Engine interface and an implied
SimpleEngine default implementation.
public interface Engine {
public void setFuelValveIntake(int gasPedalPressure);
public int getTorque;
}
public interface Car {
public int onAcceleratorPedalStep(int gasPedalPressure);
}
Highly coupled dependency
The following shows a common arrangement
with no dependency injection applied:
public class SimpleCar implements Car {
private Engine engine = new SimpleEngine;
/** @returns the car speed */
public int onAcceleratorPedalStep(int gasPedalPressure) {
engine.setFuelValveIntake(gasPedalPressure);
int torque = engine.getTorque;
int speed = ... //math to get the car speed from the engine torque
return speed;
}
}
public class MyApplication {
public static void main(String[] args) {
Car car = new SimpleCar;
int speed = car.onAcceleratorPedalStep(5);
logWithFormat("The car's speed is %s.", speed);
}
}
As shown, the
Car class needs to create an instance of an
Engine to calculate its speed based on how much pressure is made on the accelerator pedal. A similar implementation would instantiate the
Engine inside the
onAcceleratorPedalStep method, but such an approach still forces the
Car class to know how to instantiate its own
Engine.
Note that because each
Car has its own engine, it is possible to have
Car create its own. In this artificial example,
Car owns the
Engine, so this is relatively simple. In other examples where you may have a shared dependency, such as a database access subsystem, no dependent
owns the dependency, rather the dependency is
loaned. In such a case the dependency would outlive the dependent, or be shared by a large number of dependents, so each dependent could not simply create its own version without resorting to workarounds in the language, such as static methods that bypass the object's encapsulation boundary.
Manually injected dependency
Now, should this example use dependency injection, a possible implementation might be:
public class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
/** @returns the car speed */
public int onAcceleratorPedalStep(int gasPedalPressure) {
engine.setFuelValveIntake(gasPedalPressure);
int torque = engine.getTorque;
int speed = ... //math to get the car speed from the engine torque
return speed;
}
}
public class CarFactory {
public Car buildCar {
return new Car(new SimpleEngine);
}
}
public class MyApplication {
public static void main(String[] args) {
Car car = new CarFactory.buildCar;
int speed = car.onAcceleratorPedalStep(5);
logWithFormat("The car's speed is %s.", speed);
}
}
In the code above, the factory takes responsibility for assembling the car, and
injects the engine into the car. This frees the car from knowing about how to create an engine, though now the
CarFactory has to know about the engine's construction. One could create an
EngineFactory, but that merely creates dependencies among factories. So the problem remains, but has at least been shifted into factories, or builder objects.
Framework-managed dependency injection
There are several frameworks available to further automate this process, allowing a complete delegation of instantiation and marshalling of such dependencies to a "meta-factory", often called a Container or Injector. An example using such a framework could look like this:
// Note the same Car implementation, but there's a Car interface declared elsewhere
public class CarImpl extends Car {
private final Engine engine;
public CarImpl(Engine engine) {
this.engine = engine;
}
/** @returns the car speed */
public int onAcceleratorPedalStep(int gasPedalPressure) {
engine.setFuelValveIntake(gasPedalPressure);
int torque = engine.getTorque;
int speed = ... //math to get the car speed from the engine torque
return speed;
}
}
// Note no factory - just the main application
public class MyApplication {
public static void main(String[] args) {
Container c = new DependencyInjectorContainer;
c.bind(Car.class,CarImpl.class);
c.bind(Engine.class, SimpleEngine.class);
c.init;
Car car = c.getObject(Car.class); // asks injector/container for a car.
int speed = car.onAcceleratorPedalStep(5);
logWithFormat("The car's speed is %s.", speed);
}
}
In this example, a Dependency Injection Container is used, and bindings are declared between
CarImpl and
Car, then between
SimpleEngine and
Engine. After initialization, the container, when asked for a car, will note that the type of
Car registered is a
CarImpl. It then observes that
CarImpl has a constructor with one argument - an
Engine, implying that this
CarImpl must have an
Engine to be a valid
Car. It therefore attempts to create an
Engine, noting that the type of
Engine it has registered is a
SimpleEngine. It creates this (since
SimpleEngine in this example has no further dependencies), and passes that
Engine to the
CarImpl, which it then returns.
This pattern can be followed with a small or large number of declared objects and their dependencies. This basic pattern, with enhancements based on different scopes and lifecycles, is typical of most Dependency Injection frameworks.
This example does not show the full potential of dependency injection since the
CarFactory is very simple. In a simple case such as the above, managing the dependencies requires slightly more code than the naive no-injection approach. However, as components get more and more complex, or if shared instances of dependencies are required, preventing
owned dependencies from being created by their dependents, then such a framework can provide minimal code to manage a large number of objects and their creation and dependency fulfillment.
Benefits and issues
One important benefit of using dependency injection approach is the reduction of boilerplate code in the application objects since all work to initialize or setup dependencies will be made in the provider component.
Also, it offers more flexibility because it becomes easier to create alternative implementations of a given service type, and then to specify which implementation is to be used via a
configuration fileIn computing, configuration files, or config files configure the initial settings for some computer programs. They are used for user applications, server processes and operating system settings. The files are often written in ASCII and line-oriented, with lines terminated by a newline or carriage...
, without any change to the objects that use the service. This is especially useful in unit testing, because it is easy to inject a
fake implementationIn object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to...
of a service into the object being tested.
On the other hand, excessive or inappropriate use of dependency injection can make applications more complex and harder to maintain. Code that uses dependency injection can seem
magical to some developers, since instantiation and initialization of code is handled completely separately from that code's normal operation. This separation, while useful for maintenance, can also result in subtleties that can be hard to diagnose. Additionally, some dependency-injection frameworks maintain verbose configuration files, requiring that a developer understand the configuration as well as the code, where such configuration is "invisible" to
IDEAn integrated development environment also known as integrated design environment or integrated debugging environment is a software application that provides comprehensive facilities to computer programmers for software development...
-supported reference analysis and
refactoringCode refactoring is the process of changing a computer program's internal structure without modifying its external functional behavior or existing functionality, in order to improve internal quality attributes of the software...
. Some IDEs mitigate against this by providing explicit support for such frameworks. Other such frameworks provide configuration using the programming language itself, allowing such
refactoring support to apply directly. Other frameworks such as the
Grok web frameworkGrok is a web framework based on Zope 3 technology. The project was started in 2006 by a number of Zope 3 developers. Grok has since then seen regular releases...
introspect the code and use
convention over configurationConvention over Configuration is a software design paradigm which seeks to decrease the number of decisions that developers need to make, gaining simplicity, but not necessarily losing flexibility....
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.
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:
- 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.
Existing frameworks
Dependency injection frameworks exist for a number of platforms and languages, as can be seen in the following table:
See also
- Plug-in (computing)
- Strategy pattern
In computer programming, the strategy pattern is a particular software design pattern, whereby algorithms can be selected at runtime....
- Architecture description language
Different communities use the term architecture description language. Two important communities are:* The software engineering community* The enterprise modelling and engineering community...
Further reading