Dependency Inversion Principle of Design Pattern

Motivation

motivation

When we design software applications we can consider the low level classes the classes which implement basic and primary operations(disk access, network protocols,...) and high level classes the classes which encapsulate complex logic(business flows, ...). The last ones rely on the low level classes. A natural way of implementing such structures would be to write low level classes and once we have them to write the complex high level classes. Since high level classes are defined in terms of others this seems the logical way to do it. But this is not a flexible design. What happens if we need to replace a low level class?

When we design software, it can be divided into low-level classes and high-level classes. The low-level classes are used to implement basic and main operations (disk access, network protocols ...), and the high-level classes are used to encapsulate complex logic (business flow ... .), High-level classes depend on low-level classes. The natural way to implement such structures is to write low-level classes first, and then use them to implement complex high-level classes. The implementation of high-level classes based on other classes seems logical, but this design is not flexible enough, what if we want to replace low-level classes?

Let's take the classical example of a copy module which reads characters from the keyboard and writes them to the printer device. The high level class containing the logic is the Copy class. The low level classes are KeyboardReader and PrinterWriter.

Let's take a classic example: a copy module reads characters from the keyboard and then writes them to the output device. The high-level class that contains logic is the Copy class, and the low-level classes are KeyboardReader and PrinterWriter.

In a bad design the high level class uses directly and depends heavily on the low level classes. In such a case if we want to change the design to direct the output to a new FileWriter class we have to make changes in the Copy class. (Let's assume that it is a very complex class, with a lot of logic and really hard to test).

In bad design, high-level classes directly use and rely on low-level classes. In this case, if we want to change the design and output it to the new FileWriter class, we have to modify the Copy class. (We assume that the Copy class is a complex class that contains a lot of logic inside and is difficult to test).

In order to avoid such problems we can introduce an abstraction layer between high level classes and low level classes. Since the high level modules contain the complex logic they should not depend on the low level modules so the new abstraction layer should not be created based on low level modules. Low level modules are to be created based on the abstraction layer.

In order to avoid such problems, we introduce an abstraction layer between high-level classes and low-level classes. Because high-level classes that contain complex logic do not need to depend on low-level modules, new abstract layers do not need to depend on low-level modules to create them. Low-level modules are created based on the abstraction layer.

According to this principle the way of designing a class structure is to start from high level modules to the low level modules:
High Level Classes --> Abstraction Layer --> Low Level Classes

According to this principle, the design of the class structure starts from the high-level module to the low-level module.

High-level class-> Abstract layer-> Low-level class

Intent

purpose

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.
  • High-level modules should not depend on low-level modules, they should all rely on abstraction.
  • Abstraction should not depend on details, details should depend on abstractions.

Example

Examples

Below is an example which violates the Dependency Inversion Principle. We have the manager class which is a high level class, and the low level class called Worker. We need to add a new module to our application to model the changes in the company structure determined by the employment of new specialized workers. We created a new class SuperWorker for this.

The following is an example of illegal reliance on the principle of inversion. We have a high-level manager and a low-level worker. We simulate the impact of new special workers on the company structure in the form of adding new modules to the application. We create a new SuperWorker class for this.

Let's assume the Manager class is quite complex, containing very complex logic. And now we have to change it in order to introduce the new SuperWorker. Let's see the disadvantages:

  • we have to change the Manager class (remember it is a complex one and this will involve time and effort to make the changes).
  • some of the current functionality from the manager class might be affected.
  • the unit testing should be redone.

Assume that the Manager class is quite complex and contains complex logic. Now we have to modify it in order to introduce a new SuperWorker class, which has the following disadvantages:

  • We have to modify the Manager class (remember that it is complicated and requires time and effort to deal with the change).
  • Some current functions of the manager class may be affected.
  • Unit testing needs to be repeated.

All those problems could take a lot of time to be solved and they might induce new errors in the old functionlity. The situation would be different if the application had been designed following the Dependency Inversion Principle. It means we design the manager class, an IWorker interface and the Worker class implementing the IWorker interface. When we need to add the SuperWorker class all we have to do is implement the IWorker interface for it. No additional changes in the existing classes.

These problems may take a lot of time to solve, and may introduce new errors in the original functions. If the application is designed using the dependency inversion principle, the situation mentioned above will not occur. According to the principle, we need to design the manager class, the IWorker interface and the Worker class that implements the IWorker interface. When we need to add a new SuperWorker class, we only need to implement the IWorker interface for it, and no additional modifications will be made to the existing class.

 1 // Dependency Inversion Principle - Bad example
 2 
 3 class Worker {
 4 
 5     public void work() {
 6 
 7         // ....working
 8 
 9     }
10 
11 }
12 
13 
14 
15 class Manager {
16 
17     Worker worker;
18 
19 
20 
21     public void setWorker(Worker w) {
22         worker = w;
23     }
24 
25     public void manage() {
26         worker.work();
27     }
28 }
29 
30 class SuperWorker {
31     public void work() {
32         //.... working much more
33     }
34 }

 

Below is the code which supports the Dependency Inversion Principle. In this new design a new abstraction layer is added through the IWorker Interface. Now the problems from the above code are solved(considering there is no change in the high level logic):

  • Manager class doesn't require changes when adding SuperWorkers.
  • Minimized risk to affect old functionality present in Manager class since we don't change it.
  • No need to redo the unit testing for Manager class.

The following code supports the dependency lead principle. In this new design, a new abstraction layer is added through the IWorker interface. The problem in the above code is now resolved (copying has not changed in the high-level logic):

  • The Manager class does not need to be modified when adding SuperWorkers.
  • Because we do not modify the Manager class, the risk of impact on the original functions is minimized.
  • There is no need to re-unit test the Manager class.
 1 // Dependency Inversion Principle - Good example
 2 interface IWorker {
 3     public void work();
 4 }
 5 
 6 class Worker implements IWorker{
 7     public void work() {
 8         // ....working
 9     }
10 }
11 
12 class SuperWorker  implements IWorker{
13     public void work() {
14         //.... working much more
15     }
16 }
17 
18 class Manager {
19     IWorker worker;
20 
21     public void setWorker(IWorker w) {
22         worker = w;
23     }
24 
25     public void manage() {
26         worker.work();
27     }
28 }

 

Conclusion

in conclusion

When this principle is applied it means the high level classes are not working directly with low level classes, they are using interfaces as an abstract layer. In this case instantiation of new low level objects inside the high level classes(if necessary) can not be done using the operator new. Instead, some of the Creational design patterns can be used, such as Factory Method, Abstract Factory, Prototype.

When this principle is applied, it means that high-level classes do not directly depend on the work of low-level classes, they use an abstract layer interface. In this case, the lower-level class instances in the higher-level classes cannot be allocated using the operator new. We can use the creational design pattern to support this, such as factory methods, abstract factories, and prototype patterns.

The Template Design Pattern is an example where the DIP principle is applied.

The template design pattern is an example of the application of the principle of inversion.

Of course, using this principle implies an increased effort, will result in more classes and interfaces to maintain, in a few words in more complex code, but more flexible. This principle should not be applied blindly for every class or every module. If we have a class functionality that is more likely to remain unchanged in the future there is not need to apply this principle.

Of course, using this principle means that we have to spend more energy, because it will introduce more classes and interfaces that need to be maintained. In short, this principle uses more complex code in exchange for a more flexible design. This pattern should not be used indiscriminately in any class or module. If our class functions do not need to be changed in the future, we do not need to use this principle.

 

Original address: https://www.oodesign.com/dependency-inversion-principle.html

Guess you like

Origin www.cnblogs.com/wanpengcoder/p/12683285.html