In this post, I’m going to talk about DI (Dependency Injection) in a very basic way and explain exactly what is it and why we should use it. This post is aimed for those who have no idea what dependency injection is or they’re not sure why we should use it. So let’s get started.
What is a dependency?
Let’s start with an example, we have
ClassC like below:
As you can see, ClassA has an instance of ClassB so we say
ClassA is dependent on ClassB why? Because ClassA needs ClassB to do its job. We also can say
ClassB is a dependency of ClassA
Before going further I want to clarify this is a very good thing! because in an application we don’t want a class to do all the jobs. we want to separate our logic into different classes each responsible only for one thing, then these classes can work together to deliver the job.
How can we handle dependencies?
Let's consider the ways that we can handle dependencies, Generally, there are three paths we can take:
First: Handle dependencies inside the dependent class:
In a more simpler way, we can say, we can create objects whenever we need them. An example of that would be like this:
It is as simple as that! We create class whenever we need them.
- It’s very simple
- The dependent class or
ClassAhas full control over how and when to create dependency.
ClassBare tightly coupled. Meaning whenever I want to use ClassA I have to use ClassB as well, So Replacing ClassB with something else is impossible
- Any change to the way ClassB is initialized requires the change of code inside ClassA as well. (And all the other users of ClassB), So changing dependencies is hard!
- ClassA is untestable! If you care about testing and you should since it’s one of the most important aspects of software development, you need to unit test each class in isolation. Meaning when you want to see if
ClassAdoes its job right by writing some unit tests you don’t care about
ClassBor any other classes in your code, you only care about
ClassAbut as you can see in the example, whenever you create an instance of ClassA, ClassB is created as well. Now when you test a method it might fail but you don’t know if it was because
ClassAhas an error or
ClassAis fine but some code inside
ClassBmade the test to fail! Basically unit testing in impossible because units (classes) cannot be isolated.
- ClassA needs to know how to instantiate dependencies. In our example, it has to know how to create ClassC and use it to create ClassB. It’s better if it doesn’t know anything about this stuff! Why? because of the Single Responsibility Principle
Each class should be responsible for one and only one thing!
So we don’t want our classes to worry about anything other than their defined jobs, how to handle dependencies is an extra task we put on classes!
Second: Let the user class handle the dependencies
So by now, we found out handling dependencies inside a dependent class is not such a good idea, how about the dependent class defines all the dependencies it needs for example inside the constructor and let the user class provide them for it. Would it solve our problems? Let’s see.
Look at example code below:
ClassA receives all the dependencies inside the constructor and it can simply call methods on
ClassB without initializing anything!
- ClassA and ClassB are now loosely coupled and we can replace ClassB without breaking any code inside
ClassAFor example instead of passing
ClassBwe can now pass
AssumeClassBthat is a subclass of ClassB and our code will work without any issues!
- ClassA now is testable! When writing a unit test we can create our own version of
ClassB(mock object) and pass it to the
ClassAnow if some tests fail its definitely the fault in ClassA.
- ClassA doesn’t have to worry or think about dependencies and can focus on its own job!
- It’s like a chain and It should end somewhere! For example here the user of ClassA has to know all about the initialization of ClassB and to do that it has to know about initialization of ClassC as well and so on. So you see that any change to the constructor of any of this classes can result in a change in the caller class, not to mention ClassA may have more than one user, so the logic for creating objects is repeated.
- Even though our dependencies are clear and easy to understand the user code is complicated and hard to manage! So it’s not simple any more! Also, it violates the Single Responsibility Principle as the user code is now both responsible for its job and handling dependencies for dependent classes as well!
The second example is clearly better than the first one but It still has some downsides, can we do better? Before considering the third way let’s define Dependency Injection first.
What is Dependency Injection?
Dependency injection is a way that we handle dependencies outside dependent class so depended class doesn’t have to worry about it anymore!
As you can see, based on this definition, our first solution is clearly not using dependency injection, but our second solution is since the dependent class doesn’t do anything about providing dependencies, but we still consider the second solution a bad one, WHY?!
Because dependency injection definition as described here doesn’t say anything about where the dependencies should be handled (other than not inside the dependent class), It’s up to the developer to choose the right place for handling dependencies and as you can see in example 2, the user class is not the right place!
Can we do better? Let’s consider the third way we can handle dependencies:
Third: Let someone else handle dependencies for us!
In the first approach dependent classes are responsible for getting their own dependencies and in the second approach we moved handling dependency from the dependent class to the user class but what if there was someone else that could handle dependencies so neither dependent class nor the user class has to worry about it? This is the idea that allows the clean handling of dependencies in our app.
Clean implementation of Dependency Injection (my opinion)
The responsibility of handling the dependencies are given to a third party so no other part of the app should be woried about it anymore.
Dependency injection is not a technology, framework, library or anything like that, It’s just an idea! The idea of handling dependencies somewhere other than the dependent class (preferably in some dedicated part). It can be done without using any library or framework but usually, we use a framework for dependency injection because it makes it super easy to handle dependencies and avoids writing boilerplate code.
Every dependency injection framework has two essential part, It may provide so many other features as well but this two are the basics that they should have:
First, they give us a way to define fields (object) that are going to be injected, some frameworks do this by annotating the field or the constructor with
@Inject annotation and some may do it differently. By Inject we simply mean that the dependency should be handled by the DI framework. The code will look something like this:
Second, they allow us to define how each dependency should be provided and It will be done in a completely separate file (files). It may look something like this. (Again this is just an example, It may change based on your framework)
So as you see each function is responsible for handling one dependency, so if we need ClassA some place in our app our dependency injection framework is going to create one instance of ClassC by calling provideClassC, passing it to provideClassB and get an instance of ClassB then pass that to provideClassA and creates the ClassA for us! It almost seems like a magic. So let’s see what are the pros and cons.
- Dead simple! both the dependent class and the part that provide the dependencies are clear and easy to understand.
- Classes are loosely coupled and easily can be replaced with some another class. Let say we want to replace
AssumeClassCwhich is a subclass of ClassC. we simply change the provider code like this and all the places we use
ClassCare now using the new version automatically!
Note that we did not change any code inside our app only the provider method! It can not be simpler or more flexible than this!
- Highly testable! we can replace the dependencies provided by dependency injection with mock version while we’re testing and that’s very easy. In fact, dependency injection is your number one friend when it comes to testing.
- It’s a better design because now we have a dedicated part in our app for handling the dependencies and as a result, other parts can focus on doing their own job and not worrying about the dependencies.
- DI frameworks have a learning curve, So you and all the other developers working on the project should spend time and understand it well before using it effectively.
- Handling dependencies without DI is possible but it brings downside to our app.
- DI is just an idea! A powerful one, It simply says that we should handle dependencies outside the dependent class.
- It’s always better to dedicate some part of your app to DI. Many frameworks encourage this as well.
- Frameworks and libraries are not necessary for DI but they can help a lot, many of DI frameworks offer advanced features so DI can be down with the minimal amount of code!
In this post, I tried to touch the basics so you know what Dependency injection is and why we should use it. There are plenty of resources you can read to learn more about how you can apply DI in your own app.
Thanks to Michael Spitsin’s feedback, I was able to edit the original story and make it more accurate.
Also, Let me know what you think in the comment section below, Follow ME if you’re interested to read my other stories as well.
Make Activity onCreate method cleaner with help of Template Method Design Pattern
If you do Android development for a while you realize a lot of code goes in the activity onCreate method which make…