ArticleDirectory
- Problem cause
- What is the Dependency inversion principle?
- Three dependency implementation methods
- Advantages
- Summary
Design Pattern Series
- Six principles of design model (1): single Responsibility Principle
- Six principles of design patterns (2)
- Six principles of design patterns (3): Dependency inversion principle
- Six principles of design mode (4): interface isolation principle
Problem cause
Class A directly depends on Class B. to modify Class A to dependent Class C, you must modify the Class A'sCode. In this scenario, Class A is generally a high-level module responsible for complicated business logic. Class B and class C are underlying modules responsible for basic atomic operations. If Class A is modifiedProgramBring unnecessary risks. The program design that follows the Dependency inversion principle can solve this problem.
What is the Dependency inversion principle?
AbbreviationsDip (Dependence inversion principle).
Original Definition:High Level modules shocould not depend upon Low Level modules. Both shoshould depend upon quota actions. Quota actions shocould not depend upon details. Details shold depend upon quota actions.
The translation has three meanings:
- High-level modules should not depend on low-level modules, both of which should depend on their abstraction;
- Abstraction should not depend on details;
- Details should depend on abstraction.
Abstract: an abstract class or interface, which cannot be instantiated.
Details: a specific implementation class, an implementation interface, or an inherited class generated by an abstract class. The two can be directly instantiated using the new keyword.
Now we can use the instance to restore the scenario of the opening question for better understanding. The following code describes a simple scenario. Jim, as a person, has a way to get his own name, and then Jim eats an apple. The Code is as follows:
// Specific Jim human public class Jim {public void eat (Apple) {system. out. println ("Jim eat" + apple. getname () ;}}// specific Apple class public class Apple {Public String getname () {return "apple ";}} public class client {public static void main (string [] ARGs) {Jim = new Jim (); Apple = new Apple (); Jim. eat (Apple );}}
Running result: Jim eat apple
The above code looks simple, but it is actually a very fragile design. Now Jim can eat apples, but he cannot only eat apples instead of other fruits. This will definitely cause a nutritional imbalance. Now Jim wants to eat bananas (it seems that there are many potassium elements in the bananas and it is helpful to eat them). Suddenly Jim cannot eat bananas. What should he do? It seems that only the code has been modified. Because the Jim class in the code above depends on the Apple class, you have to modify the code in the Jim class. What if Jim wants to eat another fruit next time? Continue to modify the code? This processing method is obviously not desirable. frequent modification may bring great system risks. If you change it, you may find that Jim will not eat fruit.
The above embarrassing problem occurs in the code because the Jim class depends on the Apple class and the two are tightly coupled,The result is that the maintainability of the system is greatly reduced. It is intolerable to add a banana class but to modify the Jim class code. Why do you want to change your code? Obviously Jim is not happy. We often say that we need to design a robust and stable system. Here we only add a banana class, so we need to modify the Jim class. What about robustness and stability.
Based on the Dependency inversion principle, we can modify the above Code to extract the abstract part. First, we extract two interfaces: People and fruit, which provide the necessary abstract methods. In this way, whether it is Jim or apple or banana, you only need to add your own implementation classes. Because the Dependency inversion principle is followed, it only depends on abstraction and does not depend on details. Therefore, you do not need to modify other classes to add classes. The Code is as follows:
// People interface public interface people {public void eat (fruit); // each person has a way to eat, otherwise they will all starve} // fruit interface public interface fruit {Public String getname (); // fruits are named} // specific Jim human public class Jim implements people {public void eat (fruit) {system. out. println ("Jim eat" + fruit. getname () ;}}// specific Apple class public class Apple implements fruit {Public String getname () {return "apple ";}} // specific banana class public class banana implements fruit {Public String getname () {return "banana" ;}} public class client {public static void main (string [] ARGs) {People Jim = new Jim (); fruit Apple = new Apple (); fruit banana = new banana (); // here it complies with the Lee's replacement principle Jim. eat (Apple); Jim. eat (banana );}}
Running result:
Jim eat apple
Jim eat banana
- The people class is a complex business logic and belongs to high-level modules, while the fruit class is an atomic module and belongs to low-level modules. People depends on the abstract fruit interface, which achieves: high-level moduleIt should not depend on lower-layer modules. Both of them should depend on abstract (abstract class or interface).
- The people and fruit interfaces have no relationship with their respective implementation classes. Adding implementation classes does not affect the interfaces. This achieves the following: Abstract (abstract class or interface) it should not depend on details (specific implementation class ).
- The Jim, apple, and banana implementation classes all need to implement the abstract methods defined by their respective interfaces, so they depend on interfaces. This is done: the details (specific implementation class) should rely on abstraction.
What is inversion?
Here, we have a good understanding of the dependency inversion principle, but what is "inversion. This is the case. At first, according to the normal way of thinking, I want to eat bananas, that is, eating bananas, and eating apples if I want to eat apples. The same is true for programming, all are designed based on implementation-oriented thinking. Now, we need to put our thinking upside down, extract public abstractions, and implement interface-oriented (abstract class) programming. It no longer depends on the specific implementation, but on the interface or abstract class. This is the dependent way of thinking "Inverted.
Three dependency implementation methods
There are three ways to pass object dependencies:
- Declare the dependent object in the interface method. As shown in the code above.
- The constructor transmits the dependent object.The parameters to be passed in the constructor are implemented through abstract classes or interfaces. The Code is as follows::
// Specific Jim human public class Jim implements people {private fruit; Public Jim (fruit) {// The constructor transmits the dependent object this. fruit = fruit;} public void eat (fruit) {system. out. println ("Jim eat" + this. fruit. getname ());}}
- The setter method transmits the dependent object.The parameters in the setxxx method we set are abstract classes or interfaces to implement the transfer of dependency objects. The Code is as follows:
// The specific Jim human public class Jim implements people {private fruit; Public void setfruit (fruit) {// setter method transmits the dependent object this. fruit = fruit;} public void eat () {system. out. println ("Jim eat" + this. fruit. getname ());}}
Advantages
From the code modification process above, we can see that due to the loose coupling design between classes, interface-Oriented Programming relies on abstraction and does not depend on details. Therefore, when you modify the code of a class, modifications of other classes are not involved, which significantly reduces system risks and improves system robustness.
Another advantage is that in our actual project development, many people work together and each person is responsible for a certain module. For example, one person is responsible for developing the people module and one person is responsible for developing the fruit module. If the Dependency inversion principle is not adopted, the abstraction is not extracted, the person who develops the people module must wait until the fruit module is developed. Otherwise, compilation fails. This is the development of a single thread. In order to achieve parallel development, the development progress can be greatly improved by following the Dependency inversion principle and extracting abstraction.
Summary
In the end, the core of the dependency inversion principle is the idea of interface-oriented programming. We try to extract abstract and public interfaces for each implementation class to form an interface or abstract class, it depends on abstraction rather than specific implementation. The principle of dependency inversion is actually to make the implementation of classes or modules independent of each other through abstraction (abstract classes or interfaces), so as to achieve loose coupling between modules. However, this principle is also the most difficult to implement among the six design principles. If this principle is not implemented, it means that the open and closed principle (open to expansion and closed to modification) cannot be implemented.