6 min. read

July 01, 2022

5 Reasons to Use Dependency Injection in Your Code

Every developer should know hacks that will make coding easier. Is dependency injection worth a try?

Profile Photo

Yasas Sri Wickramasinghe, Lead Software Engineer (Past), Lecturer

As developers, the term "dependency injection" is probably not new to you. However, many developers use it without understanding how it works and why we need it.

So, in this article, I will discuss the top 5 reasons to use dependency injection, including practical examples, to help you decide when to use dependency injection in your project. I'll also talk about one major limitation that shouldn't be swept under the rug.

codedependency1laptop

Dependency Injection (DI) Explained

When you rely on something, we use the term "Depend" to express it. Likewise, if your code relies on another implementation or another module, you can use the technique "dependency injection."

There are 3 main types of dependency injection:

1. Constructor Injection

In this method, a class constructor is used to provide dependencies. The below code snippet shows a simple example of constructor injection.

@Component class Account { private Category category; //dependencies are provided as argumentsAccount(Category category) { Objects.requireNonNull(category); this.category = category; } Category getCategory() { return category; } }

2. Setter Injection

In this method, a setter method is used to provide dependencies to a class. The below code snippet shows a simple example of a Setter injection.

@Component class Country { privateFestival festival; //dependencies are provided as arguements to setter method@AutowiredvoidsetFestival(Festival festival) { this.festival = festival; } FestivalgetFestival() { return festival; } }

3. Field Injection

In this method, annotations are used, and dependencies are directly provided to fields using annotations. The below code snippet shows a simple example of field injection.

@Component class Engine { //injecting dependency via annotation to the field @AutowiredprivateFuel fuelType; voidFuelgetFuelType() { return fuelType; } voidsetFuelType(Fuel fuelType) { this.fuelType = fuelType; } ... }

Why is Dependency Injection useful?

Even though the dependency injection is mostly underrated among developers due to one limitation, the advantages of using this technique are highly effective.

Let's see why you should consider using dependency injection and where you need to be cautious when using the dependency injection below.

1. Highly Extensible Code

Your codebase is expected to evolve over time, and you'll often (or even constantly) have to fix bugs and defects. However, let's suppose you're using dependency injection because of its loosely coupled nature. In this case, you can improve your application quickly with far less effort.

On top of this advantage, there's another benefit due to the high extensibility of code. Because of the externally injected dependencies, developers can scale up the application without worrying about managing dependencies manually for each functionality. Instead, they can code simultaneously, making the development phase more efficient.

2. Highly Testable Code

Dependency injection helps to develop testable code, allowing developers to write unit tests easily. You can use mock databases with dependency injection, and test your application without affecting the actual database.

codedependency2

Here's an example. Suppose you want to test business logic, but it needs a database connection to execute. That's a pretty time-consuming and relatively complicated task. However, suppose you can mock the required database and isolate the actual database from the unit tests. In that case, your unit tests will become more reliable and reduce the unit testing effort a lot.

Watch React.js: The Documentary here. What did the creators of the most popular frontend framework have to say today?

3. Highly Reusable Code

The loosely coupled structure of code using dependency injection makes it easier to reuse business logic implementations in different locations around your codebase.

Suppose you need to pass an external database connection access to a method to read data from your database. You can follow the dependency injection technique and create a plug-and-play type of database access module. You can only inject this module to where it is required.

Great ZSH Terminal Plugins.

This allows you to implement more modularised code and promote reusability without any code changes.

4. Highly Readable Code

According to the dependency injection technique, you can mention all the required dependencies in a single file, which acts as an interface between the application's components. By doing so, all the dependencies are getting isolated from the actual implementation of the business logic.

Therefore, you do not need to go through all the code to figure out dependencies in your code. Instead, they're kept in a centralised place that can be easily referenced. All you have to do is check the interface. This also makes your actual implementation much more readable.

The below example shows how a repository can be injected as an external dependency. You can see that the IStudentRepository acts as an external interface to the StudentService class.

import { IStudentRepository } from'./StudentRepository.ts'; classStudentService { privatereadonlystudentRepository: IStudentRepository; //dependency injectionpublicconstructor (studentRepository: IStudentRepository { this.studentRepository = studentRepository; } publicasyncfindStudentByIndex(index: string): Promise<Student> { returnthis.studentRepository.findStudentByIndex(index); } }

In the above implementation, dependencies are isolated from the class, and you can't refer to the dependencies directly. However, you can navigate to the relevant interface and check the needful. For example, the code of the dependency may look like the below.

import { databaseDriver } from'pg-driver'; //interface exposed to use the dependencyexportinterfaceIUserRepository { addStudent(student: Student): Promise<void>; findStudentByIndex(index: string): Promise<Student>; } exportclassStudentRepositoryimplementsIStudentRepository { publicasyncfindStudentByIndex(index: string): Promise<Student> { //method implementation } }

This is a simple use case, but imagine having several dependencies to work on a single class. You will end up having large chunks of unnecessary codes within your class. Using dependency injection in these kinds of situations will be a lifesaver, helping to organize your codebase and make it more readable.

5. Highly Maintainable Code

Code maintainability is all about evolving the application, fixing bugs and defects, and feature enhancements. Using a loosely coupled design provides high maintainability for any project. If you use dependency injection correctly, your code becomes loosely coupled, making it more maintainable.

This is another tremendous advantage because having highly maintainable code will drastically reduce the total cost of ownership of the code as well as the effort required to maintain the code in the long run.

A Word of Caution Regarding Dependency Injection

Using dependency injection can be incredibly rewarding, but if you're a new developer, you should be especially careful, otherwise, you might create unexpected errors in your application.

If you're planning to inject an external dependency, you should have a good understanding of both dependency and the framework to ensure they are compatible. If not, there can be significant errors during the application runtime even if it gets compiled successfully.

As developers, we don't want to have surprises like that during application runtime, so (regardless of how cool dependency injection is), this is a significant concern.

Final Thoughts

To summarise, dependency injection can be really, really rewarding. It's much easier to manage the codebase and work together with multiple developers. Furthermore, the project's business logic implementation will be more readable, maintainable, and extensible. Meanwhile, your dependencies can be maintained separately as well.

Looking to find new roles that match your ambitions? Honeypot is Europe's job platform for developers and data specialists. Sign up today and get offers with salary and tech stack up front. (P.S. Honeypot is always free for developers.)