What is Dependency Injection?
In the real world, we have a situation where one class is dependent on the other class.
For example, if we are creating a Mobile class, we have the dependency on RAM class. In a normal scenario, we will create an instance of RAM class in the Mobile class using the new keyword. So, we can use all the functions of the RAM class in the Mobile class.
- Public class RAM
- {
- int speed=20;
- public RAM(int speed)
- {
- this.speed=speed;
- }
- }
- public class Mobile
- {
- private RAM r1;
- public Mobile()
- {
- RAM r1=new RAM(2);
- }
- }
In this example, we have created an instance r1 of RAM by calling the parameterized constructor in the mobile class.
But, there are three fundamental problems with this programming style.
This code is difficult to maintain over time.
It works well until we don't have any change in dependent class.
For example, it will work well until the RAM class is the same but in the future, if we change RAM class, then it is necessary to make a change in the Mobile class.
- Public class RAM
- {
- int speed=20,size=2;
- public RAM(int speed,int size)
- {
- this.speed=speed;
- this.size=size;
- }
- }
- public class Mobile
- {
- private RAM r1;
- public Mobile()
- {
- RAM r1=new RAM(2);
- }
- }
So, now, the Mobile class will give a compilation error because it needs 2 parameters while creating an instance but we are passing only 1 parameter.
So, every time the RAM class changes, the Mobile class also needs to be changed and it will create a big issue if we are using RAM class in many other classes or we have a dependency on many other classes like display, Battery etc. In turn, these classes may have a dependency on other classes so if any time this dependency changes, the Mobile class may need to change.
Hence, this code is difficult to maintain over time.
SOLUTION
- Public class RAM
- {
- int speed=20;
- public RAM(int speed)
- {
- this.speed=speed;
- }
- }
- public class Mobile
- {
- private RAM _r1;
- public Mobile(RAM r1)
- {
- _r1=r1;
- }
- }
In Dependency Injection, the Mobile class is not directly creating an instance of the RAM class (without using a new keyword). Instead, it just specifies that it has a dependency on RAM class using a constructor. Now, the external source is going to create an instance of RAM class and provide it to Mobile class whenever required. So now, we have an external source which is creating and injecting dependency to the Mobile class and the Mobile class doesn't need to change when the dependent class changes.
Instances of dependencies created by class are local to class and can't share data and logic.
In our example, the r1 instance of RAM is local to Mobile class and we can't share it with any other class. In this case, if we want dependency of RAM class in 5 class then we will end up creating 5 instances of RAM class. By doing this data and functionality will be local to a particular class and cannot be shared by other classes.
SOLUTION
Dependency Injection provides a singleton. It means whenever the first time instance of the class is requested, the external source will check whether it already has an instance of the class or not. If it doesn't have an instance, then it will create an instance and inject it into class. The second time, it will not create a new instance; instead, it will inject the old instance in new requested class.
So, with the help of Dependency Injection, sharing data and logic becomes much easier.
Hard to unit test without Dependency Injection.
In our example, the Mobile class has only one dependency but in the real world, one class has many dependencies which, in turn, has another dependency. With all this hierarchy of dependency, unit testing becomes difficult.
SOLUTION
With Dependency Injection, it is very easy to mock all this dependency.