Declare war on dependencies-Dependency inversion, control inversion, and dependency Injection Analysis

Source: Internet
Author: User

In the book Tao nature-object-oriented Practice Guide, we use a dialectical relationship between opposites to illustrate the "template method" model-"positive dependency. dependency inversion "(see section 15th of" tao nature "[Wang yongwu, Wang Yonggang 2004]). This view of putting the "Hollywood" principle and the "Dependency inversion" principle in an equal volume actually comes from the section on the lightweight container picocontainer homepage:
"One of the famous synonymous principles of inversion of control is by Robert C. another nickname of Martin's dependency inversion principle is the Hollywood principle (Hollywood principle: Don't call me, let me call you) "[picocontainer 2004].
After in-depth discussions with netizens on the csdn blog, I reorganized these concepts. I found that although these concepts are unified at the macro level of thinking and motivation, there are still many subtle differences at the specific application level. This article uses several simple examples to further analyze the Dependency inversion principle, inversion of control, and dependency injection concepts, it is also a supplement to the content of Tao nature.

Dependency and Coupling)

the help document of Rational Rose defines the "dependency" relationship as follows: "dependency describes the relationship between two model elements, if the dependent model element changes, it will affect another model element. Typically, in the class diagram, dependency indicates that operations of the customer class call operations of the server class ."
Martin Fowler describes coupling as follows in the article reducing coupling: "If one module that changes the Program requires the other module to change simultaneously, the two modules are considered coupled." [Fowler 2001]
from the preceding definition, we can see that if module A calls the method provided by Module B or accesses some data members in Module B (of course, in object-oriented development, we generally do not advocate this.) We think that module A depends on Module B, and modules A and Module B are coupled.
so whether dependency is a good thing or a bad thing for us?
human understanding is limited, making it difficult for most people to understand and grasp overly complex systems. The software system is divided into multiple modules, which can effectively control the complexity of the module, so that each module is easy to understand and maintain. However, in this case, information must be exchanged between modules in some way, that is, some coupling relationship must occur. If a module has no association with other modules (even if it is only a potential or implicit dependency), we can almost conclude that this module does not belong to this software system, should be removed from the system. If there is no coupling relationship between all modules, the result is: The whole software is a simple accumulation of multiple unrelated systems. For each system, all functions must be implemented in one module, which means that no module is decomposed.
therefore, there must be one or more dependencies between modules. Never imagine eliminating all dependencies. However, a strong coupling relationship (for example, a module change may cause one or more other modules to change the dependency relationship at the same time) will cause great harm to the quality of the software system. Especially when the demand changes, the maintenance cost of Code is very high. Therefore, we must try our best to control and eliminate unnecessary coupling, especially the dependency that will lead to uncontrollable changes in other modules. The principles of dependency inversion, control reversal, and dependency injection are constantly generated and developed in the arduous struggle with dependency.


 

Separation of interfaces and Implementations 

Separating interfaces from implementations is the first attempt to control dependencies. Figure 1 shows Robert C. martin's first example in dependency inversion [Martin 1996. Readkeyboard () and writeprinter () are two functions in the function library. The application calls these two functions cyclically to copy the characters you typed to the printer output.



In order to make the application program independent from the specific implementation of the function library, C language writes the definition of the function in a separate header file (function library. h. The advantage of this approach is that, although the application needs to call the function library and rely on the function library, when we want to change the implementation of the function library, we only need to rewrite the implementation code of the function number, the application does not need to change. For example, change the function library. c file, re-implement the writeprinter () function to output data to the disk. Then, as long as the application and function library are re-linked, the function of the program will change accordingly.
The preceding function library can also be implemented in C ++. We usually call this implementation with object-oriented technology the module that provides multiple support classes for applications as a "class library", as shown in figure 2. This method of eliminating the dependency between applications and class libraries through the separation interface and implementation has the following features:
1. The class library called by the application depends on the class library.
2. The separation of interfaces and implementations resolves this dependency to a certain extent, and the specific implementation can change during compilation. However, this solution has very limited functions. For example, a system cannot accommodate multiple implementations, but different implementations cannot change dynamically. It is strange to use the writeprinter function name to implement the function output to the disk.
3. The class library can be reused separately. However, applications cannot be reused without the class library unless a class library that implements the same interface is provided.


Dependency inversion (dependency inversion principle) 

It can be seen that the method of simple separation interface discussed above has very limited effect on the dissolution of dependencies. Java provides pure interface classes, which do not include any implementation code and can better isolate two modules. Although this pure interface class is not defined in the C ++ language, all member functions are pure virtual functions and abstract classes do not contain any implementation code, which can play a role similar to the Java interface class. To be different from the simple interface mentioned in the previous section, the interface defined based on the Java interface class or the C ++ abstract class is called an abstract interface. The principle of dependency inversion is based on abstract interfaces. Robert Martin describes the Dependency inversion principle [Martin 1996]:
A. Upper-layer modules should not depend on lower-layer modules. They both depend on an abstraction.
B. abstraction cannot depend on visualization, while it depends on abstraction.
To eliminate the dependency between two modules, an abstract interface should be defined between the two modules. The upper-layer module calls the function defined by the abstract interface, and the lower-layer module implements the interface. 3. For the example in the previous section, we can define two abstract classes: reader and writer. The read () and write () functions are pure virtual functions, the keyboardreader and printerwriter classes implement these interfaces. When an application calls the read () and write () functions, the implementation of the keyboardreader and printerwriter classes is actually called due to the role of the polymorphism mechanism. Therefore, abstract interfaces isolate the specific classes in applications and class libraries so that there is no direct coupling between them and they can be expanded or reused independently. For example, we can use a similar method to implement the filereader or diskwriter class. The application can either input from the keyboard or file as needed or output to the printer or disk, you can even complete Multiple Input and Output tasks at the same time. It can be concluded that this method of resolving the dependency between applications and class libraries through abstract interfaces has the following characteristics:
1. The application calls the abstract interface of the class library and relies on the abstract interface of the class library. The specific implementation class derives from the abstract interface of the class library and also relies on the abstract interface of the class library.
2. the implementation of applications and specific class libraries is completely independent, and there is no direct dependency between them. As long as the interface class is stable, the specific implementation of applications and class libraries can change independently.
3. The class library can be reused independently, and the application can work together with any class library that implements the same abstract interface.


Generally, because the designer of the class library does not know how the application uses the class library, abstract interfaces are mostly summarized by the class library designer according to the typical usage mode they have imagined and retain a certain flexibility, to be used by application developers.
However, there is another situation. Figure 4 is an example [Fowler 2001] used by Martin Fowler in the article "cutting coupling". The domain package must use the database package, that is, the domain package depends on the database package. To isolate the domain package and database package, you can introduce a Mapper package. If we want the domain package to be reused multiple times and the Mapper package can change at any time under specific circumstances, we must prevent the domain package from being overly dependent on the Mapper package. At this time, the designers of the domain package can summarize the abstract interfaces (such as store) they need, and the designers of the Mapper package can implement this abstract interface. In this way, the dependencies are completely reversed at the interface level and the implementation level.


Inversion of Control) 

The dependency between the application and the class library is described above. If we develop a framework system instead of a class library, the dependency will be stronger. So how can we eliminate the dependency between the framework and applications?
Chapter 5th of "tao nature" describes the differences between the framework and class libraries:
"The most important difference between a framework and a class library is that a framework is a semi-finished application, and a class library only contains a series of classes that can be called by applications.
"The Class Library provides users with a series of reusable classes. These classes are designed in line with the object-oriented principles and patterns. You can create instances of these classes, or inherit new Derived classes from these classes, and then call the corresponding functions in the class. In this process, the class library always passively responds to users' call requests.
"The framework implements a basic and executable architecture for a specific purpose. The framework already contains the main process from application startup to running. The steps in the process that cannot be predefined are left to users for implementation. When the program is running, the framework system automatically calls user-implemented functional components. At this time, the framework system is active.
"We can say that the class library is dead, and the framework is live. The application calls the class library to complete specific functions, and the Framework calls the application to implement the entire operation process. The framework is the perfect embodiment of the principle of control inversion ."
A best example of a framework system is a graphical user interface (GUI) system. A simple GUI system developed using a process-oriented design method is shown in Figure 5.


As shown in figure 5, the application calls the createwindow () function in the GUI framework to create a window. Here, we can say that the application depends on the GUI framework. However, the GUI framework does not know how to process the window message received by the window. This is only the most clear for the application. Therefore, when the GUI framework needs to send window messages, it must call a specific window function defined by the application (such as mywindowproc in ). In this case, the GUI framework must depend on the application. This is a typical bidirectional dependency. This kind of Bidirectional dependency is very serious: the GUI framework cannot exist independently because the GUI Framework calls a specific function (mywindowproc) in the application; for a new application, most of the GUI framework needs to be modified accordingly. Therefore, how to eliminate the dependency between the framework system and applications is the key to implementing the framework system.
Not only object-oriented methods can solve this problem. Win32 API has long provided us with examples to solve similar problems under the process-oriented design idea. The architecture model of the Win32 class is shown in Figure 6.


In Figure 6, when an application calls the createwindow () function, it must pass a pointer to the message processing function to the GUI framework (for Win32, the pointer is recorded in the window information structure in the GUI framework. When a window message needs to be sent, the GUI Framework calls the window function through this pointer. Compared with figure 5, the GUI framework still needs to call the application, but this call changes from a hard-coded function call to a dynamic call that is registered by the application in advance by the called object. Figure 6 shows this dynamic call with a dotted line. It can be seen that this dynamic call relationship has a great advantage: When an application changes, it can change the call target of the framework system on its own, without the need to change the GUI framework. Now, we can say that although there are still calling relationships from the GUI framework to the application, the GUI framework is no longer dependent on the application. This kind of dynamic calling mechanism is also called a callback function ".
In the Object-Oriented field, the alternative of "callback function" is "template method mode", that is, "Hollywood principles (do not call us, let us call you )". An Object-Oriented Implementation of the GUI Framework 7 is shown.


In Figure 7, "Gui framework abstract interface" is an interface provided by the GUI framework system to applications. The motive for abstracting this interface is to eliminate the direct dependency between the application and the GUI framework based on the "Dependency inversion" principle, to minimize the impact of changes in the GUI framework on applications. Window Interface Class is the core of "template method mode. When the application calls the createwindow () function, the GUI framework saves the reference of the window in the window linked list. When a window message needs to be sent, the GUI Framework calls the sendmessage () function of the window object, which is a non-virtual member function in the window class. The sendmessage () function calls the windowproc () virtual function. Here, the windowproc () function is actually implemented in the mywindow class of the application. In Figure 7, we can no longer see the direct dependency between the GUI framework and applications. Therefore, the template method fully implements the dynamic call mechanism of the callback function, eliminating the dependency between the framework and the application.
From the above analysis, we can see that the template method mode is the basis of the framework system, and any framework system cannot do without the template method mode. Martin Fowler also said [folwer 2004], "the authors of several lightweight containers are proud to say to me that these containers are very useful because they implement a 'control invert '. I am deeply confused by the rhetoric of this statement: the inversion of control is a common feature of the Framework. If only control reversal is used, these lightweight containers are considered to be different, it's like saying, 'my car is different because it has four wheels '. The key to the problem is: which aspects of the control are reversed? The first control reversal I encountered was the control right on the user interface. Early user interfaces were completely controlled by applications. You designed a series of commands in advance, such as 'input name' and 'input address'. The application output prompt information one by one, and retrieve the user's response. In the graphic user interface environment, the UI framework is responsible for executing a main loop. Your application only needs to provide event processing functions for various areas of the screen. Here, the main control right of the program is reversed: From the application to the Framework ."
Indeed: as shown in figure 3 and figure 7, when using a common class library, the main loop of the program is located in the application, and the application using the framework system does not include a main loop, only interfaces defined by some frameworks are implemented. The framework system is responsible for implementing the main cycle of system operation and calling applications in template mode when necessary.
That is to say, although "Dependency inversion" and "control inversion" are both effective methods to eliminate module coupling at the design level, both try to make specific and variable modules rely on the basic principle of abstract and stable modules. However, there are differences in the Use context and focus of the two modules: "Dependency inversion" emphasizes "inversion" of the traditional hierarchical concepts originating from process-oriented design ideas, while "control inversion" emphasizes the reversal of control over the program flow process; the "Dependency inversion" is more widely used to describe program processes (such as the master-slave and hierarchical relationships of processes ), it can also be used to describe other design models with conceptual levels (such as service components and customer components, core modules and peripheral applications ), "Control inversion" is only applicable to scenarios where the process control is described (suchAlgorithm Control of a process or business process ).
In a sense, we can also regard "control inversion" as a special case of "Dependency inversion. For example, the "control inversion" Mechanism Implemented in the template method is to abstract an interface class between the framework system and the application to describe the prototype of all algorithm steps, the framework system depends on the interface class to define and implement the program process. The application depends on the interface class to provide the implementation of specific algorithm steps, the application's dependency on the framework system is "Inverted" as the dependency between the two on the abstract interface.
In general, the dependency between applications and framework systems has the following characteristics:
1. The application and framework system are actually two-way calls and two-way dependencies.
2. The dependency inversion principle can weaken the dependency between applications and frameworks.
3. "control reversal" and specific template method modes can dissolve the dependency between the framework and the application, which is also the basis of all framework systems.
4. The framework system can be reused independently.

Dependency Injection) 

In the previous example, we used the "Dependency inversion" principle to minimize the dependency between the application copy class and the Read and Write services provided by the class library. However, if you need to implement the copy () function in the class library, what will happen? Assume that a "service class" is implemented in the class library, and the "service class" provides the copy () method for application use. When using an application, first create an instance of the "service class" and call the copy () function. The keyboardreader and printerwriter class instance objects will be created when the "service class" instance is initialized. 8.


As shown in figure 8, although the reader and writer interfaces isolate the "service class" and the specific reader and writer classes, the coupling between them is minimized. However, when the "service class" creates a specific reader and writer object, the "service class" still depends on the specific reader and writer objects. in figure 8, the blue dotted line is used to describe the dependency.
In this case, it is critical to instantiate the specific reader and writer classes and minimize the dependency of the Service Classes on them. If the service class is in the application, this dependency will not affect us much. However, when the "service class" is located in a class library that needs to be independently released, its code cannot change as the application changes. This also means that if the "service class" is excessively dependent on the specific reader and writer classes, you cannot add new reader and writer implementations on your own.
To solve this problem, "dependency injection" is used to cut off the dependency between the "service class" and the specific reader and writer classes, and the application injects this dependency. 9.


In Figure 9, the "service class" is not responsible for creating instance objects of the specific reader and writer classes, but for creating instances by applications. When an application creates a "service class" instance object, it injects the reference of the specific reader and write object into the "service class. In this way, the code in the "service class" is only related to the abstract interface. When the specific implementation code changes, the "service class" will not change. When adding a new implementation, you only need to change the application code to define and use the new reader and writer classes, this type of dependency injection is also called "constructor injection ".
If an injection interface is abstracted for the copy class, the application injects dependency through the interface. This injection method is usually called "interface injection ". If a set-value function is provided for the copy class, the application injects Dependencies by calling the set-value function. This method of dependency injection is called "Set-value injection ". For details about "interface injection" and "Set Value injection", refer to [Martin 2004].
Both picocontainer and spring lightweight container frameworks provide corresponding mechanisms to help users implement different "dependency injection ". In addition, they also support defining dependencies in XML files in different ways, and then the application calls the framework to inject dependencies. When the dependencies need to change, you only need to modify the corresponding XML file.
Therefore, the core idea of dependency injection is:
1. Abstract interfaces isolate the dependencies between users and implementations, but creating instance objects for specific implementation classes still results in dependency on specific implementations.
2. Dependency injection can be used to eliminate such creation dependencies. After dependency injection is used, some classes are completely written based on abstract interfaces, which can adapt to the changes to the greatest extent possible.

Conclusion 

separation interfaces and implementations are the first attempts to effectively control dependencies. pure abstract interfaces better isolate the two modules that depend on each other, the "Dependency inversion" and "control inversion" principles describe the motives for using abstract interfaces to eliminate coupling from different perspectives. The gof design model is the perfect embodiment of this motion. The creation process of a specific class is another common dependency. In the "dependency injection" mode, you can set the creation process of a specific class to a proper place, this mechanism is similar to the gof creation mode.
these principles provide good guidance for our practice. However, they are not the Bible and may vary on different occasions, we should use it flexibly in the development process based on the possibility of changing requirements.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.