Dependence Inversion Principle (DIP, DI, IoC)
For the software design, there are five principles called S.O.L.I.D, which stands for
- Single Responsibility Principle
- Open-closed Principle
- The Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
In Object-Oriented programming, those principles play an important roles and give us the guideline to design loosely coupled classes. Along with dependency inversion principle(DIP), there are some terms called Inversion of Control(IoC) and Dependency Injection(DI). These are very abstract concepts.
Dependence Inversion Principle (DIP)
First, let’s see the definition of the DIP:
1.High-level modules should not depend on low-level modules. Both should depend on abstractions.
2.Abstractions should not depend upon details. Details should depend upon abstractions.
It is really a abstract concept. Let’s first figure out what is dependency. The dependency is kind of a requirment. For example, a person needs a fork to eat that is the penson depends on the fork. When we coding, we can write like this
1 | class Fork { |
Person holds a Fork variable. What if we want to use a spoon to eat, we probably will write code like following
1 | class Spoon { |
You can see that we need to change lots of places to let this person to use spoon to eat. If we need to use chopsticks, we also need to change chunk of codes. Here is the dependency hierarchy.1
2
3person -> spoon
-> fork
-> chopsticks
This hierarchy violates the first point of DIP
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Let’s take a look of these cases. The person trys to call the use function of these tools, which is the only care. Person does not need to care about how the use function implemented that is the job of the tool class. So this use function is the abstract behavior of the tools, while what exactly the tool is should be the details. Here comes to the second point.
Abstractions should not depend upon details. Details should depend upon abstractions.
Depend on these two points. We use an interface for use function and all tools should implemented this function.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public interface Tool {
public void use()
}
class Fork implements Tool {
public void use() {
System.out.println("Using fork to eat");
}
}
class Spoon implements Tool {
public void use() {
System.out.println("Using spoon to eat");
}
}
class Person {
private Tool tool;
public Person() {
tool = new Spoon();
}
public void eat() {
tool.use();
}
}
Now the Person does not depend on the Fork or other low-level detail while it depends on the Tool interface. Both high-level and low-level modules depend on interface. It looses coupling between modules. The hierarchy should be like this.1
2
3person -> tool <- spoon
(abstract) <- fork
<- chopsticks
Next the person wants to use other tool to eat, we can simple implemented this interface. This is detail depends on abstraction.
Inversion of Control (IoC)
The Person class still has the control to instantiate the tool. So everytime we need to change a tool to eat, we have to change the code inside the Person constractor. For the Inversion of Control, Person should move the instantiation of the tool outside its class.
1 | class Person { |
Now this Person class will not need to be modified whenever the tools changed (It does not care). It gives the authority to the Test which is the container. This is the IoC. Let’s change the Person class a little bit.
1 | class Person { |
Person and Tool are loosely coupled and do not depend on each other. Any person can use any tool to eat. In Java Spring/Dagger, we set up the xml or use java refection to instantiate/inject (usually by annotation) in the IoC container.
Dependence Injection (DI)
IoC is our purpose and dependence injection is how we do it. In the code above we used contractor injection. If we use a setter, it is called setter injection.1
2
3
4
5
6
7
8
9
10
11class Person {
private Tool tool;
public Person() {
}
public void eat() {
tool.use();
}
public void setter(Tool tool) {
this.tool = tool;
}
}
Also we can also use interface injection. It is very similar with setter. With interface injection, we can only inject the tool to the person want to eat.
Inversion of Control is the design pattern followed the Denpendency Inversion Principle and the Dependency Injection is the way to do it.