Declaring war on dependency [go]

Source: Internet
Author: User

Ultimate goal: Decoupling

The core idea of dependency injection is : interface and implementation separation
1. The abstract interface isolates the dependencies between the consumer and the implementation, but creating an instance object of the concrete implementation class will still result in a dependency on the specific implementation.
2. Use dependency injection to eliminate this creation dependency. With dependency injection, some classes are written entirely based on abstract interfaces, which can best accommodate changes in demand.

The separation of interfaces and implementations is an initial attempt to effectively control dependencies , while purely abstract interfaces better isolate the two interdependent modules, and the "dependency inversion" and "inversion of control" principles describe the motives of the abstraction interface to dissolve the coupling from different angles, GOF's design model is the perfect embodiment of this motive. The creation of a specific class is another common dependency, and the "Dependency injection" pattern can be used to centralize the creation of a specific class into a suitable location, which is similar to the GOF creation pattern.
These principles are a good guide to our practice, but they are not Bibles and may vary in different contexts, and we should use them flexibly in the development process, depending on the likelihood of changes in demand.

Analysis of dependency inversion, control inversion and dependency injection
In the book "Tao Nature-an object-oriented practice Guide", we use a opposites dialectical relationship to illustrate the "template method" mode-"positive dependence vs. dependency inversion" (see: "Tao Nature", chapter 15th [Wang Yongwu, Wang Yinggang 2004]). The idea of equate the "Hollywood" principle and the "dependency inversion" principle comes from a paragraph on the Picocontainer home page of the lightweight container:
One of the famous synonymous principles of control inversion (inversion of controls) is the dependency inversion principle proposed by Robert C. Martin (Dependency inversion Principle), Another nickname for it is the Hollywood principle (Hollywood Principle: Don't call me, let me call You) "[Picocontainer 2004].
and netizens in CSDN blog on the deep discussion, I again put these concepts re-comb a bit. I found that although these concepts are unified at the macro level of thinking and motivation, there are still many subtle differences in the specific application level. In this paper, the concepts of dependency inversion (Dependency inversion Principle), controlling inversion (inversion of control), Dependency injection (Dependency injection) are further analyzed through a few simple examples. , but also for the "Tao Nature" text content of a supplement it.

Dependency and coupling (Dependency and coupling)

In the book "Tao Nature-an object-oriented practice Guide", we use a opposites dialectical relationship to illustrate the "template method" mode-"positive dependence vs. dependency inversion" (see: "Tao Nature", chapter 15th [Wang Yongwu, Wang Yinggang 2004]). The idea of equate the "Hollywood" principle and the "dependency inversion" principle comes from a paragraph on the Picocontainer home page of the lightweight container:
First look at the concept of dependency and coupling.
The "dependency" relationship is defined by Rational Rose's Help Document: "Dependency describes the relationship between two model elements, and if the dependent model element changes, it affects another model element." Typically, on a class diagram, a dependency indicates that the operation of the client class invokes the operation of the server class. ”
Martin Fowler describes the coupling in the article "Reducing coupling": "If one module of the change program requires the other module to change simultaneously, the two modules are considered to be coupled." "[Fowler 2001]
As can be seen from the above definition: if module a invokes the method provided by module B, or accesses some data members in module B (which is not generally advocated in object-oriented development), we assume that module a relies on module B, and that there is a coupling between module A and Module B.
So, is reliance a good thing or a bad thing for us?
Due to the limited understanding of human beings, it is difficult for most people to understand and grasp overly complex systems. The software system is divided into several modules, which can effectively control the complexity of the module, so that each module is easy to understand and maintain. However, in this case, the module must exchange information in some way, that is, some kind of coupling relationship is bound to occur. If a module has no association with other modules (even if it is a potential or implied dependency), we can almost conclude that the module is not part of this software system and should be removed from the system. If there is no coupling between all the modules, the result must be: The whole software is simply a stack of disparate systems, and for each system, all functions are implemented in a single module, which equals no decomposition of any module.
Sothere must be one or the other dependencies between the modules, never fantasize about eliminating all dependencies. However, an over-strong coupling relationship (such as a change in one module that causes one or more other modules to change simultaneously) can cause great harm to the quality of the software system. Especially when requirements change, the cost of maintaining code is very high. Therefore, we must try our best to control and eliminate unnecessary coupling, especially the kind of dependency that causes the other modules to have uncontrollable changes. The principles of dependency inversion, inversion of control, and dependency injection are the continuous emergence and development of people in the arduous struggle with dependence.

Interface and implementation separation

Separating interfaces from implementations is the first attempt to control dependencies, and Figure 1 is the first example of Robert C. Martin in the article "dependency inversion" [Martin 1996]. where Readkeyboard () and WritePrinter () are the two functions in the library, the application loops through the two functions to copy the characters typed by the user to the printer output.

In order for the application to be independent of the specific implementation of the function library, the C language writes the definition of the function in a separate header file (function library. h). The advantage of this approach is that while the application is calling the library and relies on the library, when we want to change the implementation of the function library, the application does not need to change as long as the implementation code of the function is overridden. For example, changing the function library. c file, the WritePrinter () function is re-implemented into the output of the disk, as long as the application and function library relink, the function of the program will change accordingly.
The libraries above can also be implemented using the C + + language. We typically implement this with object-oriented technology, and the modules that provide multiple support classes for an application are called "Class libraries", as shown in 2. This approach to resolving dependencies between applications and class libraries by separating interfaces and implementations has the following characteristics:
1. The application calls the class library, which relies on the class library.
2. The separation of interfaces and implementations has, to a certain extent, dispelled this dependency, and the implementation can vary during compilation. However, the effect of this digestion method is very limited. For example, a system can not accommodate multiple implementations, different implementations can not change dynamically, with the WritePrinter function name to achieve the function of the output to disk is very strange, and so on.
3. Class libraries can be reused separately. However, an application cannot be reused from a class library unless it provides a class library that implements the same interface.

Dependency inversion (Dependency inversion Principle)

It can be seen that the simple method of separating interfaces discussed above is very limited to the digestion of dependency relationships. The Java language provides a purely interface class that does not include any implementation code and can better isolate two of modules. Although this pure interface class is not defined in the C + + language, all member functions are abstract classes that are pure virtual functions and do not contain any implementation code, which can act like a Java interface class. To distinguish it from the simple interface mentioned in the previous section, the interface defined by the Java interface class or C + + abstract class is called an abstract interface later in this article. The dependency inversion principle is based on an abstract interface. Robert Martin describes the principle of dependency inversion [Martin 1996]:
  A. The upper modules should not be dependent on the underlying modules, which collectively rely on an abstraction.
B. Abstractions cannot depend on the image, and the image relies on abstraction.
The implication is that in order to dissolve the dependencies between the two modules, an abstract interface should be defined between the two modules, and the upper module invokes the function defined by the abstract interface, which is implemented by the lower module. 3, for the example in the previous section, we can define two abstract classes of reader and writer as abstract interfaces, where the read () and write () functions are pure virtual functions, and the specific Keyboardreader and Printerwriter classes implement these interfaces. When the application calls the read () and write () functions, the actual invocation is implemented in the specific Keyboardreader and Printerwriter classes due to the role of the polymorphism mechanism. As a result, abstract interfaces isolate specific classes in applications and class libraries so that there is no direct coupling between them and can be extended or reused independently. For example, we can implement the FileReader or Diskwriter class in a similar way, and the application can either choose to input from the keyboard or file as needed, or it can choose to output to the printer or disk, or even perform many different input and output tasks at the same time. It can be concluded that this abstraction of the dependency between the application and the class library through an abstract interface has the following characteristics:
  1. The application invokes the abstract interface of the class library, relies on the abstract interface of the class library, and the concrete implementation class derives from the abstract interface of the class library, and also relies on the abstract interface of the class library.
2. The application and the specific class library implementation is completely independent, there is no direct dependency on each other, as long as the interface class to maintain the stability of the application and the implementation of the class library can be independently changed.
3. The class library can be reused entirely independently, and the application can work with any class library that implements the same abstract interface.

  It is summarized as relying on abstraction or interface, not relying on concrete ...., thus reducing the coupling dependency.

In general, because the class library designer does not know how the application will use the class library, the abstract interface is mostly summed up by the class library designer based on the typical usage patterns that they envision, and retains a certain degree of flexibility to be used by the developers of the application.
But there is another situation. Figure 4 is an example of Martin Fowler used in the article "Reducing coupling" [Fowler 2001]. Where the domain package uses the database package, that is, the domain package relies on the database package. To isolate domain packages and database packages, you can introduce a mapper package. If, under certain circumstances, we want the domain package to be reused multiple times, and the mapper package can change at any time, then we must prevent the domain package from relying too heavily on the mapper package. At this point, the designer of the domain package can summarize the abstract interfaces (such as the store) that they need, and the designer of the mapper package implements the abstract interface. In this way, the dependencies are completely reversed, both at the interface level and at the implementation level.

Controlled inversion (inversion of control)

The dependency between the application and the class library is described earlier. If we are not developing a class library, but a framework system, the dependencies will be stronger. So how do you dissolve the dependencies between the framework and the application?
The 5th chapter of "Tao Nature" describes the difference between a framework and a class library:
"The most important difference between the framework and the class library is that the framework is a ' semi-finished ' application, and the class library contains only a series of classes that can be called by the application.
The class library provides users with a series of reusable classes that are designed to conform to object-oriented principles and patterns. When used by users, you can create instances of these classes, or inherit new derived classes from those classes, and then invoke the corresponding functionality in the class. In this process, the class library always responds passively to the user's call request.
The framework implements a basic, executable architecture for a specific purpose. The framework already contains the main processes from the start to the run of the application, and the steps that cannot be predetermined in the process are left to the user. When the program runs, the framework system automatically calls the user-implemented functional components. At this point, the behavior of the framework system is active.
"We can say that the class library is dead, and the framework is alive." The application accomplishes a specific function by invoking the class library, while the framework implements the entire operation process by invoking the application. The framework is the perfect embodiment of the control inversion principle. ”
One of the best examples of framework systems is the graphical user interface (GUI) system. A simple, GUI system developed using a process-oriented design approach is shown in 5.

As can be seen from Figure 5, the application calls the GUI framework of the CreateWindow () function to create the window, where we can say that the application relies on the GUI framework. However, the GUI framework does not understand how the window should be handled after it receives a window message, which is only the most obvious application. Therefore, when the GUI framework needs to send a window message, it must also invoke a specific window function defined by the application (such as Mywindowproc in). At this point, the GUI framework must also be dependent on the application. This is a typical two-way dependency relationship. This two-way dependency has a very serious flaw: since the GUI framework invokes a particular function (Mywindowproc) in the application, the GUI framework simply cannot exist independently; for a new application, the GUI framework will mostly be modified accordingly. Therefore, how to eliminate the dependence of the framework system on the application is the key to implement the framework system.
Not only object-oriented methods can solve this problem. The WIN32 API has long provided us with examples of solving similar problems in a process-oriented design approach. The class WIN32 is shown in Schema Model 6.

In Figure 6, when the application calls the CreateWindow () function, a pointer to a message handler function is passed to the GUI framework (for WIN32, we pass this pointer when registering the window Class), and the GUI framework records the pointer in the window information structure. When a window message needs to be sent, the GUI framework invokes the window function through the pointer. The GUI framework still needs to invoke the application when compared to Figure 5, but this call changes from a hard-coded function call to a dynamic call by the application to register the called object in advance. Figure 6 shows this dynamic invocation with a dashed line. As you can see, this dynamic invocation relationship has a great advantage: when the application changes, it can change the call target of the framework system itself, and the GUI framework does not have to change. Now, we can say that although there is a call relationship from the GUI framework to the application, the GUI framework is no longer dependent on the application at all. This dynamic invocation mechanism is also often referred to as a "callback function".
In the object-oriented realm, the substitute for "callback function" is "template method pattern", i.e. "Hollywood principle (don't call us, let us call you)". An object-oriented implementation of the GUI framework is shown in 7.

In Figure 7, the GUI Framework abstraction interface is the interface that the GUI framework system provides to the application. The motivation to abstract the interface is to dissolve the direct dependency between the application and the GUI framework based on the principle of "dependency inversion", so that the changes in the GUI framework implementation are minimized for the application. The Window interface class is the core of the template method pattern. When the application calls the CreateWindow () function, the GUI framework saves the window's references in the window list. When a window message needs to be sent, the GUI framework invokes the SendMessage () function of the Window object, which is a non-virtual member function implemented in the window class. The SendMessage () function also calls the WindowProc () virtual function, which actually executes the WindowProc () function implemented in the application Mywindow class. In Figure 7, we have not seen a direct dependency between the GUI framework and the application. Therefore, the template method pattern fully implements the dynamic invocation mechanism of the callback function, which dissolves the dependency between the framework and the application.
From the above analysis, it can be seen that the template method is the basis of the framework system, any framework system is inseparable from the template method model. Martin Fowler also said [Folwer 2004], "the authors of several lightweight containers proudly said to me: These containers are very useful because they achieve ' inversion of control '. I am puzzled by this: control reversal is a feature common to the framework, and if you think that these lightweight containers are different just because you use control inversion, it's like saying ' My car is different because it has four wheels '. The crux of the problem is: what controls do they reverse? My first exposure to the control reversal is for the user interface of the master control. Early user interface is completely controlled by the application, you pre-designed a series of commands, such as ' Enter Name ', ' Enter address ', and so on, the application output prompt information, and retrieve the user's response. In a graphical user interface environment, the UI framework is responsible for executing a main loop, and your application simply provides event handlers for each area of the screen. Here, the main control of the program has been reversed: from the application to the framework. ”
True: As you can see from Figure 3 and Figure 7, the main loop of the program is in the application when using the normal class library, and the application that uses the framework system no longer includes a main loop, but implements some framework-defined interfaces, and the framework system is responsible for implementing the main loop of system operation. and invoke the application through the template method mode when necessary.
That is to say, although "dependency inversion" and "inversion of control" are effective methods to dissolve module coupling at the design level, they are also the basic principles of trying to make concrete and variable modules dependent on abstract, stable modules, but there are differences between the use context and the focus point: "dependency inversion" emphasizes on the traditional, From the "inversion" of the hierarchical concept oriented to process design, "inversion of control" emphasizes the reversal of the control of the program flow; the use of "dependency inversion" is more extensive and can be used to describe the process flow, such as the master-slave and hierarchical relationship of the process. It can also be used to describe other design models that have conceptual hierarchies (such as service components and customer components, core modules and peripheral applications, etc.), while inversion of control applies only to situations that describe process control (such as algorithmic processes or control of business processes).
In a sense, we can also think of "inversion of control" as a special case of "dependency inversion". For example, the "control reversal" mechanism implemented by the template method pattern is actually an interface class that describes the prototype of all the algorithm steps between the framework system and the application, and the framework system relies on the interface class to define and implement the program flow, and the application relies on the interface class to provide the implementation of the concrete algorithm steps. The application's dependency on the framework system is "inverted" to its dependency on the abstract interface.
Generally speaking, the dependencies between application and framework systems have the following characteristics:
1. The application and framework systems are actually two-way calls, two-way dependent relationships.
2. The dependency inversion principle can weaken the dependency between the application and the framework.
3. The "Inversion of control" and the specific template method pattern can dissolve the framework-to-application dependencies, which is the basis of all framework systems.
4. The framework system can be reused independently.

Dependency Injection (Dependency injection)

In the previous example, we minimized the dependency between the application copy class and the service read,write provided by the class library through the "dependency inversion" principle. But what happens if I need to implement the copy () function in the class library? Suppose you implement a "service class" In a class library, and the "service class" provides a copy () method for use by your application. When an application is used, the instance of the service class is created first, and the copy () function is called. Instance objects for the Keyboardreader and Printerwriter classes are created when an instance of the service class is initialized. As shown in 8.

As can be seen from 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 has a dependency on the specific reader and writer objects-this dependency is described in the blue dashed line in Figure 8.
  In this case, how to instantiate the specific reader and writer classes, while minimizing the service class dependency on them, is a very critical issue. If the service class is in the application, the impact of this dependency on us is not significant. But when the service class is in a class library that needs to be published independently, its code cannot change as the application changes. This also means that if the "service class" relies too much on specific reader and writer classes, users cannot add new reader and writer implementations themselves.
   The solution to this problem is "dependency injection", that is, to sever the dependency between the "service class" and the specific reader and writer classes, and the application to inject this dependency. As shown in 9.

In Figure 9, the "service class" is not responsible for creating specific instance objects for reader and writer classes, but is created by the application. When an application creates an instance object of the service class, the reference to the specific reader and write object is injected inside the service class. In this way, the code in the "service class" is related only to the abstract interface. The "service class" will not change when the code changes. When you add a new implementation, you can also define and use the new reader and writer classes by simply changing the application's code, which is often referred to as "constructor injection."
If an injection interface is abstracted specifically for the copy class, the application injects dependencies through the interface, which is often referred to as "interface injection." If you provide a set value function for the copy class, the application injects a dependency by calling the Set value function, which is called "Set value injection". Refer to [Martin 2004] for specific "interface injection" and "SetPoint injection".
The Picocontainer and spring lightweight container frameworks provide a mechanism to help users achieve a variety of "dependency injections." And, in different ways, they also support defining dependencies in an XML file, and then the application calls the framework to inject the dependency, and when the dependency needs to change, just modify the appropriate XML file.
Therefore, the core idea of dependency injection is:
1. The abstract interface isolates the dependencies between the consumer and the implementation, but creating an instance object of the concrete implementation class will still result in a dependency on the specific implementation.
2. Use dependency injection to eliminate this creation dependency. With dependency injection, some classes are written entirely based on abstract interfaces, which can best accommodate changes in demand.

Conclusion

The separation of interfaces and implementations is an initial attempt to effectively control dependencies, while purely abstract interfaces better isolate the two interdependent modules, and the "dependency inversion" and "inversion of control" principles describe the motives for resolving the coupling using abstract interfaces from different angles, and the GOF design pattern is the perfect embodiment of this motive. The creation of a specific class is another common dependency, and the "Dependency injection" pattern can be used to centralize the creation of a specific class into a suitable location, which is similar to the GOF creation pattern.
These principles are a good guide to our practice, but they are not Bibles and may vary in different contexts, and we should use them flexibly in the development process, depending on the likelihood of changes in demand.

Declaring war on dependency [go]

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.