Dependency Inversion
Tushar Goel
Lead Member Of Technical Staff @ Salesforce | Staff Software Engineer, Technical Lead
Dependency ingestion is ‘D’ in the SOLID design principle. It is a guideline that helps design loosely coupled classes. This principle states that High-level classes should not depend upon the concrete implementation of low-level classes. Both should depend on abstractions (ex Interfaces).
Dependency injection is the implementation technique to achieve Dependency Inversion. There are a couple of ways through them we can achieve these:
1.???Constructor injection
2.???Field injection
3.???Parameter injection
?Let’s take an example to understand why we need DI.
Consider 3 different animals like Cat, Dog, Horse that makes different types of noises.
interface SoundBehaviour {
??? void makeNoise();
}
class DogBehaviour implements SoundBehaviour {
??? @Override
??? public void makeNoise() {
??????? System.out.println("dog noise...");
??? }
}
class CatBehaviour implements SoundBehaviour {
??? @Override
??? public void makeNoise() {
??????? System.out.println("Cat noise...");
??? }
}
class HorseBehaviour implements SoundBehaviour {
??? @Override
??? public void makeNoise() {
??????? System.out.println("Horse noise...");
??? }
}
If we now add this behavior in their specific animal classes then we are defining concrete implementation in the individual classes.
public interface Animal {
??? void makeNoise();
}
class Cat implements Animal {
??? private CatBehaviour catBehaviour = new CatBehaviour();
??? @Override
??? public void makeNoise() {
??????? catBehaviour.makeNoise();
??? }
}
class Dog implements Animal {
??? private DogBehaviour dogBehaviour = new DogBehaviour();
??? @Override
??? public void makeNoise() {
??????? dogBehaviour.makeNoise();
??? }
}
class Horse implements Animal {
??? private HorseBehaviour horseBehaviour = new HorseBehaviour();
??? @Override
??? public void makeNoise() {
??????? horseBehaviour.makeNoise();
??? }
}?
In the above example, Animal is a high-level class and SoundBehaviour is a low-level class. In this approach following are the issues observed:
- High-level classes are dependent upon the low-level classes implementation that makes them tightly coupled.
- We are promoting inheritance that may have some side effects
- Unit testing is very difficult
?So in such scenarios, DI principle helps us out. This principle states that High-level classes should depend upon low classes through abstraction only.
We can use dependency injection here to make classes lose coupled.
class Animal {
??? private SoundBehaviour soundBehaviour;
??? public Animal(SoundBehaviour soundBehaviour) {
??????? this.soundBehaviour = soundBehaviour;
??? }
??? public void makeNoise() {
??????? soundBehaviour.makeNoise();
??? }
}
Then we can instantiate Animal class with any required behavior and the animal objects will start behaving like that.
Animal dog = new Animal(new DogBehaviour());
Animal cat = new Animal(new CatBehaviour());
dog.makeNoise();
cat.makeNoise();
?The advantage of doing this is as follow:
- ?High-level classes (Animal) is now depending upon low-level class abstraction
- Unit test cases are easy now (just mock the object, you can get the required behavior)
- Composition is used instead of inheritance
- By doing this, we are now can change the behavior at run time without making any changes in code.
So, as we can see using DI, we can design a system less couple for each other.