ivangeorgiev.dev

Introduction to Dependency Injection

Learn about DI in an intuitive way

February 16th, 2025 3 minute read

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.