Rahul Dwivedi
Introduction to Dependency Injection
05.05.20203 Min Read — In Coding

Dependency Injection

In this article, we are going to discuss Dependency injection. We will also learn what are the benefits of using dependency injection.

Introduction

Dependency injection a type of design pattern that allows classes to receive dependencies rather than creating them on their own.

What is a Dependency?

A Dependency refers to any object that a class needs in order to be fully functional. An example would be, suppose a car manufacturer produces a car. For this example, suppose the car manufacturer produces everything needed to manufacture car other than the tyres. For tyres, It depends on some other company. Without tyres, the car is incomplete. Tyres here are a dependency for the car.

We have now a clear idea of what dependency is. Let's look at this through some code.

Suppose you have class that depends on some other object for its execution. Lets looks at the following example

class ChocolateCone{
    GetCone(): string {
        return "Chocolate cone";
    }    
}

class IceCreamParlour{
    cone: ChocolateCone;
    constructor(){
        this.cone = new ChocolateCone();
    }    
    Buy(flavour: string){
        return  `Icecream with a scoop of ${flavour} and ${this.cone.getCone()}` ;
    }
}

const icecreamParlour = new IceCreamParlour();
icecreamParlour.buy("strawberry"); //Icecream with a scoop of strawberry and Chocolate cone.

Let's look at the above code.

  1. We have a class ChocolateCone that gives us Chocolate cone.
  2. We have an ice cream parlour that gives icecreams 😋. Here you can see it Depends on the ChocolateCone class for providing cones for the ice cream.
  3. In order to buy ice cream, we create an IceCreamParlor object and then call the Buy method on it and we get our Strawberry ice cream with a chocolate cone.

This class works well for this specific scenario but let's suppose, we don't want chocolate cones, we want waffle cones, we realize that there is a problem. IceCreamParlour is tightly coupled with ChocolateCone. To get a waffle cone, we will have to change the constructor and create a waffle cone instead of Chocolate Cone.

    ...
        constructor(){
            this.cone = new WaffleCone();
        }
    ...

As we can see this won't be ideal scenario to start modifying the code everytime dependency requirement changes.

This is where Dependency injection comes into play.

Dependency Injection

Since we have understood the problem, let's look at how we can use Dependency injection to solve this. Dependency Injection states If we change the constructor of our parlour class and supply the dependencies to it rather than letting it create itself, we can overcome this problem.

interface ICone {
    GetCone():string;
}
class IceCreamParlour{
    cone: ICone;
    constructor(cone:ICone){
        this.cone = cone;
    }    
    Buy(flavour: string){
        return  `Icecream with a scoop of ${flavour} and ${this.cone.GetCone()}` ;
    }
}

Here, the IceCreamParlour expects a cone and does not creates it itself. This is how we have resolved the problem. Whatever cone we want we can supply it to IceCreamParlour at the time of object creation.

class ChocolateCone implements ICone{
    GetCone():string{
        return "Chocolate cone";
    }
}
class WaffleCone implements ICone { 
    GetCone():string{
        return "Waffle cone";
    }
}

const waffleParlour = new IceCreamParlour( new WaffleCone() );
waffleParlour.buy("strawberry"); // Icecream with a scoop of strawberry and Waffle cone;

const chocolateParlour = new IcecreamParlour(new ChocolateCone())
chocolateParlour.Buy("vanilla") // Icecream with a scoop of Chocolate Cone.

When we are creating an object of the class, we are injecting the dependencies into the object. The class does not have to worry about it. If you observe, we have taken control of creating dependency from the class. This is also called Inversion of Control. The class does not have control over the creation of the object. It just specifies in constructor its dependencies and at the time of creating class instance dependency is created and served to it.

Some Benefits of using DI

  1. Helps in reusability and readability.
  2. Solves the problem of tight coupling, produces loosely coupled code.
  3. Helps in writing Unit tests or mocking behaviours.

With this, you have a clear idea of what Dependency injection and how you can use it to create decoupled and reusable classes. It is also mentioned in S.O.L.I.D principles, the D stands for DIP - Dependency inversion Principle.

Most of the time you will end up using a DI container. You can easily find a dependency injection container according to your language preference if you want to use this principle in your projects. One such DI container is tsyringe for Typescript projects.