Introduction to Dependency Injection
Learn about DI in an intuitive way
Dependency Injection (DI for short) is a really useful mechanism in software development. It is the software development version of real life delivery services. I think we can all agree that delivery services are useful in real life (e.g. food delivery). Let's learn about DI and what benefits it brings to software development.
The term Dependency Injection is a two word construct. Let's break it
down.
What is a dependency? A dependency is something that is necessary for
someone to complete their work. In real life we might be hungry and
need to eat. In this case we are dependent on food. In software
development component A (e.g. a class) might have a dependency on
component B. Because component A is dependent on component B, it's not
able to complete its work without the help of component B.
What is injection?
Injection refers to the way that dependencies
are acquired by those who need them. Let's first discuss how
dependencies are acquired when DI is not used.
When DI is not
used it's up to the one that depends on something to acquire the
dependency on their own. In real life if we are hungry and need to
eat, it's up to us to prepare the food before we eat it. In software
development if class A depends on class B, it's up to class A to
create an instance of class B.
When DI is used things are easier
for those who depend on something. All they have to do is declare that
they need something and that dependency will be "injected" into them
by an outside source. In real life if we are hungry and need to eat,
instead of preparing the food ourselves we could order it from a food
delivery service. In software development if class A depends on class
B, class A will be provided an instance of class B from an outside
source. This outside source could be a DI library or part of the
language/framework that is used.
Here are two code examples. The top one does not use DI and the bottom one does. In the top one the Human class acquires its dependency (an instance of the Food class) by creating it itself. In the bottom one the Human class accepts its dependency through its constructor (it does not create it itself).
// No Dependency Injection
class Human {
private Food food;
private boolean hungry;
public Human() {
this.food = new Food();
this.hungry = true;
}
public void eat() {
this.food.eat();
this.hungry = false;
}
}
// With Dependency Injection
class Human {
private Food food;
private boolean hungry;
public Human(Food food) {
this.food = food;
this.hungry = true;
}
public void eat() {
this.food.eat();
this.hungry = false;
}
}
In the example above we are using constructor based DI. As the name implies the dependency is injected through the constructor. Dependencies can also be injected through setter methods and through the methods that use the dependency.
class Human {
private Food food;
private boolean hungry;
// Constructor based DI
public Human(Food food) {
this.food = food;
this.hungry = true;
}
// Setter based DI
public void setFood(Food food) {
this.food = food;
}
// Method based DI
public void eat(Food food) {
food.eat();
this.hungry = false;
}
}
What are the benefits of using DI?
One benefit of using DI is that components that depend on other
components are not burdened with the creation of their dependencies.
The creation of the dependencies could be really simple, but it could
also be complex if the dependencies themselves have dependencies. The
first components would have to create instances of all the needed
components. With DI the components are free to do their job without
worrying about their dependencies.
Another benefit is that the components are not tightly coupled to
specific implementations of their dependencies. The components could
be injected different implementations of their dependencies in
different scenarios (e.g. different implementations for production and
test environments).
Another benefit is that instances of components that are
commonly dependent on can be reused. When DI is used it's common that
one instance is created (i.e. singleton) and that instance is provided
to all components that have a dependency on its type.
If you enjoy my articles, please consider supporting me.