Dependency injection: Difference between revisions
→Code illustration using Java: Re-work example to show no-injection, manual-injection, and framework-managed-injection. |
|||
Line 14: | Line 14: | ||
== Code illustration using Java == |
== Code illustration using Java == |
||
Using the car/engine example above mentioned, the following Java code shows a typical arrangement '''with no dependency injection applied''': |
|||
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 a an implied SimpleEngine default implementation. |
|||
<source lang="java"> |
<source lang="java"> |
||
public class Car { |
|||
public interface Engine { |
|||
private Engine engine = new Engine(); |
|||
public void setFuelValveIntake(int gasPedalPressure); |
|||
public int getTorque(); |
|||
} |
|||
public interface Car { |
|||
public int onAcceleratorPedalStep(int gasPedalPressure); |
|||
} |
|||
</source> |
|||
===Highly coupled dependency=== |
|||
The following shows a common arrangement '''with no dependency injection applied''': |
|||
<source lang="java"> |
|||
public class SimpleCar implements Car { |
|||
private Engine engine = new SimpleEngine(); |
|||
/** @returns the car speed */ |
/** @returns the car speed */ |
||
Line 31: | Line 56: | ||
} |
} |
||
public class MyApplication { |
|||
//omitting the Engine class since it isn't relevant to this example |
|||
//or its implementation is unknown |
|||
public static void main(String[] args) { |
|||
Car car = new SimpleCar(); |
|||
int speed = car.onAcceleratorPedalStep(5); |
|||
logWithFormat("The car's speed is %s.", speed); |
|||
} |
|||
} |
|||
</source> |
</source> |
||
As shown, the Car class needs to create an instance of an Engine |
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 <code>onAcceleratorPedalStep</code> method, but such an approach still forces the Car class to know how to instantiate its own Engine.' |
||
Note that because there 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 it's own version without resorting to workarounds in the language, such as static methods which bypass the object's [[Encapsulation (computer science)|encapsulation]] boundary. |
|||
Now, should this example use dependency injection, a possible implementation |
===Manually injected dependency=== |
||
Now, should this example use dependency injection, a possible implementation might be: |
|||
<source lang="java"> |
<source lang="java"> |
||
public class Car { |
public class Car { |
||
private Engine engine; |
private final Engine engine; |
||
public void setEngine(Engine engine) { |
public void setEngine(Engine engine) { |
||
Line 58: | Line 93: | ||
} |
} |
||
//omitting the Engine class since it isn't relevant to this example |
|||
//or its implementation is unknown |
|||
public class CarFactory { |
public class CarFactory { |
||
public Car buildCar() { |
public Car buildCar() { |
||
Car car = new Car(); |
Car car = new Car(new SimpleEngine()); |
||
} |
|||
Engine engine = new Engine(); |
|||
car.setEngine(engine); |
|||
} |
|||
return car; |
|||
public class MyApplication { |
|||
public static void main(String[] args) { |
|||
Car car = nnew CarFactory().buildCar(); |
|||
int speed = car.onAcceleratorPedalStep(5); |
|||
logWithFormat("The car's speed is %s.", speed); |
|||
} |
} |
||
Line 74: | Line 113: | ||
</source> |
</source> |
||
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. |
|||
In this example, the CarFactory class represents the provider. It is a simple application of the factory method design pattern that makes it possible for the Car class to not need to know how to get an engine for itself. This is now CarFactory's responsibility. |
|||
===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: |
|||
<source lang="java"> |
|||
// Note the same Car implementation, but there's a Car interface declared elsewhere |
|||
public class CarImpl extends Car { |
|||
private final Engine engine; |
|||
public void setEngine(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,SimpleCar.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); |
|||
} |
|||
} |
|||
</source> |
|||
In this example, a Dependency Injection Container is used, and bindings are declared between SimpleCar 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 SimpleCar. It then observes that SimpleCar has a constructor with one argument - an Engine, implying that this SimpleCar 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 SimpleCar, 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. |
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 == |
== Benefits and issues == |
Revision as of 15:20, 4 August 2009
It has been suggested that Dependency inversion principle be merged into this article. (Discuss) Proposed since May 2009. |
Dependency injection (DI) in computer programming refers to the process of supplying an external dependency to a software component. It is a specific form of inversion of control where the concern being inverted is the process of obtaining the needed dependency. The term was first coined by Martin Fowler to more clearly describe the mechanism.[1]
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 services life cycle but just have a reference to an implementation of the said services and invoke its relevant behaviors. Dependency injection is a design pattern 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 pattern involves at least three elements: a dependent, its dependencies and a provider. The dependent is an object that is expected to accomplish a relevant task in a computer program. 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 compose the dependent 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 locator, an abstract factory, a factory method or a more complex structure, like a framework.
As a simple example of the above explanation, we can think of a car as the dependent, 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 assembling 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 principle.
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 a 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 there 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 it's own version without resorting to workarounds in the language, such as static methods which 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 void setEngine(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() {
Car car = new Car(new SimpleEngine());
}
}
public class MyApplication {
public static void main(String[] args) {
Car car = nnew 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 void setEngine(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,SimpleCar.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 SimpleCar 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 SimpleCar. It then observes that SimpleCar has a constructor with one argument - an Engine, implying that this SimpleCar 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 SimpleCar, 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[2].
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 file, 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 implementation of a service into the object being tested.
On the other hand, excessive use of dependency injection can make applications more complex and harder to maintain: in order to understand the application's behaviour the developer needs to look at the configuration as well as the code, and the configuration is "invisible" to IDE-supported reference analysis and refactoring unless the IDE specifically supports the dependency injection framework. 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.
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 which the framework uses to inject the dependency.
- Type 3 or constructor injection, in which the dependencies are provided through the class constructor. This is the main form used by PicoContainer, although it also supports setter injection.
It is possible for other frameworks to have other types of injection, beyond those presented above.[4]
Existing frameworks
Dependency injection frameworks exist for a number of platforms and languages, as can be seen in the following table:
See also
Further reading
- A beginners guide to Dependency Injection
- What is Dependency Injection? - An alternative explanation - Jakob Jenkov
- Dependency Injection & Testable Objects: Designing loosely coupled and testable objects - Jeremy Weiskotten; Dr. Dobb's Journal, May 2006.
- Design Patterns: Dependency Injection -- MSDN Magazine, September 2005
- Writing More Testable Code with Dependency Injection -- Developer.com, October 2006
- Domain Specific Modeling (DSM) in IOC frameworks
- The Rich Engineering Heritage Behind Dependency Injection - Andrew McVeigh - A detailed history of dependency injection.
- P of EAA: Plugin
References
This article includes a list of references, related reading, or external links, but its sources remain unclear because it lacks inline citations. (October 2007) |