👨🏼💻 What is Dependency Injection❓ Benefits❓Implementation Methods❓| with Code Examples
Hi folks 👋. In this article, we’ll talk about Dependency Injection. We’ll look for answers of some questions such as What is the Dependency Injection❓ Why we should care about it❓ Benefits❓ What are the methods to implement DI❓
In the real world, we need some tools, objects, things for completing some tasks. So while we modelling the real world to programming as objects we need some other objects for doing some things. So for completing our main job we depend on some other objects, processes, functionalities etc. That’s exactly what dependency is.
Dependency is some required object instances which we depend on them & we need them in order to completing somethings.
For instance; in the real world a developer needs to ☕, 💻, IDE (can’t find emoji 😊). While modelling this developer to programming, the developer class has to have them. In the above class, the Developer class needs a Coffee class instance, a Computer class instance, an IDE class instance and if needed so on… In the above class coffee , computer and integratedDevEnv are dependencies.
In this method, the class creates needed dependencies by itself. For instance above Developer class creates and initializes its dependencies coffee, computer, integratedDevEnv by itself.
In this method, dependencies are injected from outside of the class into the class. As you see in the below, the class requests its needed dependencies while it's being created.
Dependency Injections is a design pattern that allows us to separate the creation and use of dependencies. It allow us to manage dependencies easily.
By this, we can inject required dependencies at run-time instead of creating them in the needed class. Also, it is the implementation of Dependency Inversion — SOLID’s 5th principle — .
Self Construction is quite simple. No big efforts or costs. And also the main class — eg. _Developer — _class has full control over how and when to create dependencies. But has some cons as below;
- It violates to Single Responsibility Principle ; The Developer class needs to know how to create dependencies and also how to use them together. That’s too much responsibility. Each class should be responsible for just one functionality, responsibility.
- Untestable ; a) When a unit test failed we don’t know what is the actual reason. Is the reason is Developer class or another dependent class- created runtime dependencies-. b) Dependencies don’t replaceable. So when we want to mock the dependencies with different variants of them for testing different scenarios we just can’t do that. Because the dependencies are hard so the class preventing to using a test double. For instance ; if we want to check the productivity of a Developer with a Latte instead of an Espresso, a Mac instead of a Matebook or VSCode instead of Android Studio we just can’t implement this scenario easily. Because dependencies are not replaceable. https://medium.com/media/c2282babe0a1b173f157dfe5a08bd7c8/href
- Tightly Coupling ; Developer class is tightly coupled to Coffee, Computer, IDE classes. So when we want to use the Developer class we have to use Coffee, Computer, IDE classes. And also we can’t replace them with other variants of one. Also when a change occured in dependency classes, all dependent classes have to change. So maintainability is hard.
With Dependency Injection, we try to resolve the above problems. Also has cons like needing more time.
Benefits of the Implementing Dependency Injection are ; ease of testing, reusability of code, ease of refactoring, reducing boilerplate code, help to loose coupling and so on…
Let’s take a closer look at some of them.
One of the key benefits of DI is that it makes testing easier. But how becomes easier? Is it just a magic wand? Let’s look at how testing becomes easier phase by phase and see the side benefits of DI ( Dependency Injection ).
Encouraging Single Responsibility ; DI is force us a little bit to separate responsibilities. Each class should be responsible for its functionality. No need to worry about creating dependencies.
Resolving Tightly Coupling ; By separating the responsibilities of the classes we will have loosely coupled classes.
Encouraging Making Classes Replaceable ; DI enables us to use d_ependency inversion_ and rely on higher-level abstractions. By this, we can prepare the different implementations of our classes and pass instances of them to the needed classes. So dependencies will be easily replaceable with different implementations. Let’s make an example for more understanding.
Firstly we write our classes as above. It manages its dependencies. Also, it does its responsibilities. Okay, let's abstract our dependency classes Coffee, Computer, IDE.
— Actually taking a slip is not the responsibility of the Coffee class. But for simplicity I add it here —
We abstracted our classes. When we have to a different variant of Coffee such as Espresso, Americano, Latte etc. we need just implement this interface. Same thing for IDE and Computer classes. Now we can prepare easily different implementations as below.
Okay, we abstracted our classes but replaceability is how working?
When we want to create a MobileDeveloper — or BackEndDeveloper — we can pass any coffee instance or any computer instance or any IDE instance to it. Dependencies are easily replaceable. And also dependencies are reusable for different developers such as below.
Now our classes are so flexible by Dependency Inversion and implementation of DI. We depend on high-level abstract classes and inject a specific implementation at run-time, based on the required use case.
Ease of test ; We can easily inject mock instances to our classes for imitation of different scenarios thanks to the easy replaceability of dependencies.
- Dependencies can be used several times.
- Thanks to each class having one functionality — Single Responsibility— we can use classes in different areas.
- Classes are now separated and isolated. One change will not affect a lot of code lines.
- If we want to change an implementation detail such as migrating from Firebase to HMS Core we can easily create a new class and inject an instance of a new class from our dependency providing center (for ex; Module classes in Hilt).
There are some benefits of DI such as above.
When some classes are instantiated by the system/library/framework we have to set dependencies by setter functions or by builder pattern.
In this method, we pass needed dependencies while creating an instance of a class. We can do that in 2 ways as below.
Before creating our target class instance we create dependencies of its. Then we pass dependencies while creating the target class instance.
For creating a MobileDeveloper instance we have to prepare dependencies of it.
- There is so boilerplate code. We have to create dependencies whenever we want to use the class.
- It violates the Single Responsibility Principle. When a class wants to use an instance of MobileDeveloper it will be responsible both for its job and managing dependencies.
- A change in MobileDeveloper class will affect a lot of areas. So maintainability is hard. Management is hard.
- We have to know which class needs which dependencies or we will look at the class for needed dependencies.
Did you think as
Wtf 💩. I just want to create a shit instance, use it and just focus to my main job
Yeah, you are totally right. And I agree with you🤗. Also, I have a solution for you in order to solve this. Let’s move to the next title.
In the previous title, we created, provided and managed dependencies ourselves. But there are various libraries to do that behalf of us. There are many features of them and they have 2 key functionality almost all.
- Allowing us to define how each dependency should be provided. Syntax etc depends on the library which you use.
Each function is responsible for handling one dependency. When we need an instance of MobileDeveloper the DI library will create an instance of Coffee by calling provideCoffee() and pass it to provideMobileDeveloper(). The same thing goes on for computer and ide params. When all params are provided, provideMobileDeveloper() function will create an instance of MobileDeveloper. Finally, the DI library will give us the instance provided by provideMobileDeveloper() function whenever we need it.
- Allowing us to define variables that are going to inject. We just define dependencies that we need and the library will generate boilerplate code for us and provide all needed dependencies to us. When we need a MobileDeveloper instance we just need to inform the library. And then use it without worrying about its dependencies etc. So cool, isn’t it 😎?
- Creating and using dependencies are separated. The layer of providing the dependencies is separated, clear and easy to understand. Other parts of the app don’t worry about dependencies. They just focus on their jobs.
- Easy maintenance because the creation of dependencies is centralized in some files. And the rest of the app using here. So when we change the provider function it will affect all of the code which uses it. So no any manually refactoring.
- Classes are loosely coupled. They can easily be replaceable. So easy testable. If we want to testing to mobileDevelopers with Latte instead of Americano we just need to change fun provideCoffee() = Latte(). That’s it. — Keep in mind, for doing that we need high-level abstractions. —
- No boilerplate code in our codebase because it is handled by the library.
- More time cost
- A little bit learning curve
Dependency Injection is not a magic wand. It will be so significant, useful, and applicable when we care about SOLID, Clean Coding Principles and Modular Architecture etc. It is just an idea of handling dependencies somewhere instead of the dependent class. It will be good practice if we dedicate a part of our app to DI. For achieving that, many libraries, frameworks help us. Of course, we can do that without any 3rd party library or framework. But libraries, frameworks decrease a lot of boilerplate code and make easier management of dependencies.
So let’s continue our development life with implementing DI via libraries, frameworks.
Happy Coding 👩💻.Thanks for reading🤗. Have a healthy life. See you in the next articles👋.