DEV Community

Cover image for How to understand Dependency Injection?
Jasen
Jasen

Posted on

How to understand Dependency Injection?

Dependency Injection (DI) is a design pattern that manages dependencies by providing (injecting) the required objects (dependencies) to a class from the outside, instead of the class creating them internally.

First, let’s briefly dive into the history.

  • In 1994, the GoF in their book "Design Patterns: Elements of Reusable Object-Oriented Software" introduced the principle of "Inversion of Control", although they did not call it Dependency Injection, but they defined the principle that an object should not manage its own dependencies, but instead obtain them from outside.
  • In early 2004, Martin Fowler systematized the idea of ​​DI, presenting it as an implementation of IoC.
  • In 2004, Spring brought the concept of DI to the Java world. Spring was the first framework to implement DI as a container.

history of Dependency Injection

So, why was it necessary?

It's just because DI solves problems. Let's find it out on NestJS.

1. Memory efficiency
Nest.js creates each dependency only once (if using a singleton), and it can be reused across the application. For example:

class Engine {}
class Car {
  constructor(private engine: Engine) {}
}

@Module({
  providers: [Engine, Car],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Here, Engine is created only once, even if Car is used in multiple places.

2. Ease of testing
You can easily mock dependencies for testing:

const mockEngine = { start: jest.fn() };
const car = new Car(mockEngine as Engine);
Enter fullscreen mode Exit fullscreen mode

3. Scalability
Instead of tightly coupled objects, you can easily add or replace dependencies in one place.

If this concept still feels challenging, let’s simplify it.

Dependency Injection

Imagine a chef who needs ingredients. Without DI, the chef grows the vegetables, raises the chickens, and milks the cows. It’s chaos, they’re exhausted, and your dinner is late.

With DI, the chef gets ingredients from a fancy supermarket. They only need to say, "I need chicken and vegetables," and it’s delivered fresh and ready to cook.

In this case, ingredients (Dependencies):

import { Injectable } from '@nestjs/common';

@Injectable()
export class Vegetables {
  get() {
    return 'Fresh vegetables';
  }
}

@Injectable()
export class Chicken {
  get() {
    return 'Organic chicken';
  }
}
Enter fullscreen mode Exit fullscreen mode

Chef (Service):

import { Injectable } from '@nestjs/common';
import { Vegetables } from './vegetables';
import { Chicken } from './chicken';

@Injectable()
export class Chef {
  constructor(private vegetables: Vegetables, private chicken: Chicken) {}

  cookDish(): string {
    const veggies = this.vegetables.get();
    const meat = this.chicken.get();
    return `Cooked dish with ${veggies} and ${meat}`;
  }
}

Enter fullscreen mode Exit fullscreen mode

Restaurant (Controller):

import { Controller, Get } from '@nestjs/common';
import { Chef } from './chef';

@Controller('restaurant')
export class RestaurantController {
  constructor(private chef: Chef) {}

  @Get('order')
  takeOrder(): string {
    return this.chef.cookDish();
  }
}

Enter fullscreen mode Exit fullscreen mode

Module:

import { Module } from '@nestjs/common';
import { Vegetables } from './vegetables';
import { Chicken } from './chicken';
import { Chef } from './chef';
import { RestaurantController } from './restaurant.controller';

@Module({
  providers: [Vegetables, Chicken, Chef],
  controllers: [RestaurantController],
})
export class RestaurantModule {}

Enter fullscreen mode Exit fullscreen mode

What happens?

  • Client request: User sends a GET request to /restaurant/order.
  • Controller: RestaurantController receives the request and calls the cookDish() method in Chef.
  • Service: Chef requests the ingredients (Vegetables and Chicken) via DI.
  • DI container: NestJS creates instances of Vegetables and Chicken and passes them to Chef.
  • Result: The string returned is: Cooked dish with Fresh vegetables and Organic chicken.

Conclusion

Dependency Injection is the foundation for creating a non-obvious, scalable, and testable architecture. By using DI, you can spend less time managing dependencies and more time solving business problems.


AI did the heavy lifting by injecting code at my request, while I discovered the recipe, cooked the visuals, and added some extra spice to the writing.

Top comments (0)