Difference between dependency injection and dependency inversion using Typescript

Difference between dependency injection and dependency inversion using Typescript

A newer version of the article will be published soon.

Introduction

In the world of software engineering, the terms dependency inversion and dependency injection are frequently used. You've probably heard it more than once. It's common to get dependency injection and dependency inversion mixed up.

In this article, we will discuss the key differences between dependency inversion and dependency injection so that you can incorporate these concepts into your project as soon as possible.

Description

First and foremost, let us revise our definition of dependency inversion. According to dependency inversion, higher-level and lower-level modules should rely on abstraction. If you've read my previous article on solid principles, you'll be familiar with the abstraction we achieved by implementing the interface. Let's look at the diagram again.

As you can see, we have three classes: A, B, and C, which are the lower-level classes where we write all of our concrete implementations. Our business logic is referred to as concrete implementations. Then there's a higher-level class that exposes the abstract method for calling the concrete class methods. However, our main goal is for both the higher-level module and the lower-level module to rely on abstraction.

We use an interface to accomplish this. The higher-level module uses a method provided by the interface, and the lower-level modules implement the method provided by the interface. This is known as Dependency Inversion. So, how about dependency injection? Dependency injection is a technique for achieving dependency inversion. Let's look at a diagram to see how dependency injection works.

The dependency injection is indicated by an arrow pointing to the higher-level module. Yes, this is where dependency injection comes in handy. We are injecting the object of a concrete class-type interface here. To help you understand, here is a sample typescript code.


export default interface IHospitalManagement{
    hirePeople():void;
}

We will now implement the interface in order to build our concrete classes.

export default class Doctor implements IHospitalManagement{
    hirePeople(): void {
        console.log("Management hired Doctor");        
    }
}
export default class Nurse implements IHospitalManagement{
    hirePeople(): void {
        console.log("Management hired Nurse");   
    } 
}
export default class Receptionist implements IHospitalManagement{
    hirePeople(): void {
        console.log("Management hired Receptionist");   
    }
}
export default class AmbulanceDriver implements IHospitalManagement{
    hirePeople(): void {
        console.log("Management hired Ambulance driver");
    }
}

By implementing the interface in our concrete classes, we make them depend on the interface, which means we make them depend on abstraction. We have now completed the lower portion of the diagram. Let's see how our higher-level class will depend on the "Abstraction" interface.

export default class Management{
//here we are injecting the object of concrete classes
    startHiring(candidate:IHospitalManagement){
        candidate.hirePeople();
    }
}

In this higher-level class, you can see that we are passing an object of type "IHospitalManagement" and calling a method "hirePeople" provided by the interface.

We use the method provided by the interface in the higher-level class by injecting the concrete class object, so it becomes a dependency. Thus we could say that the higher level class is also dependent on interface "abstraction". This is accomplished through dependency injection, which involves injecting the concrete class object into the higher-level class. Let's see our final diagram

Conclusion

The main goal is to achieve dependency inversion, and dependency injection is a method of achieving dependency inversion.

Thank you