Dependency Inversion PrinciPle
Note: This is the article published on C Report, he believes that the rendering principle is the root cause of many superiority of object-oriented technology, and the cornerstone of Design Patterns. Original text: http://www.objectmentor.com/Resources/articles/dip.pdf
As a homework, Wu Lanfang, Jin Yan, Ma Wei, Duan Yayu, Yan Shan, Yan Zhe translated, and finally YQJ2065 did some modifications.
Note 2: 2019.12, I will tell the mistakes in words. In order to read the convenience and translation [criticism], it is my annotation.
Reference: 2.1.5 Above Dependency Inverse Principle
This is the 3 article I [Robert C. Martin] is written by C Report (THE C Report), this article will be published in this column will mainly discuss the use of C and OOD, and software Some points in the project. I will strive to write an article that is effective and useful for software engineers. In these articles, I will use the newly proposed unified symbol (Version 0.8) [UML's predecessor] to design the object-oriented design. Next, this figure provides a brief description of the identification (Figure).
§1 Introduction
My last article (1996-3) discussed the Liskov Substitution Principle, LSP. This principle is applied to C , providing guidance for public inheritance. It clarifies: Each function, uses (operation) at the base class reference or pointer, it should be able to use the derived class in the base class, (or even), do not need to know the derived class. This means that: the virtual function of the subclass must count on the corresponding functions of the base class, while ensuring no less than (the corresponding function of the base class). This also means that the virtual member functions present in the base class must appear in the derivative class, and they must be useful. When this principle is violated, the type of the actual object is used to use the function on the reference or pointer to the base class to ensure that they (these functions) can be applied correctly [this actual object Above it. And this - need to check the type, in violation of the opening and closing principle (OCP), we discussed in January last year. In this column (article), we discuss the structural inferion of OCP and LSP (The Structural Implications). This structure - as a result of strictly use these principles - can be summarized as a principle, I call it "Dependency Inverse Principle" (DIP). [Robert C. Martin: I first stumbled on this principle when Jim Newkirk and I were arranging the source code directories of a C project We realized that we could make the directories that contained detailed code depend upon the directories that contained abstract classes This.. Seemed like an inversion to me, so i coined the name "dependency inversion". 】2 What is the problem?
Most of us have such a unpleasant experience, trying to deal with some software pieces that "bad design" [design is very water]. Some people in us even have more unpleasant experiences, found that we are the author of the "bad design" software. What caused a bad design? Most software engineers do not create "bad design" as starting point, but most software eventually fall to this point, and the pronunciation is not designed to be designed. How did this happen? Is it a bad design in the beginning, or the design is actually deteriorated - just like a broken meat? The core of this topic is that we lack the right definition: what is "bad" design.
"Bad design" definition
Have you demonstrated a software design that is proud of yourself, let the peer comment? Are those companions complaining with a mocking tone, such as "Why do you want this way to do this?" This kind of thing is really happening in me, I also saw it happened to many other engineers. Undoubtedly, the engineers who have different opinions do not use the same standard to define what is "bad design". The most common standard I have ever seen is TNTWIWHDI, that is, "That's Not The Way I Would Have Done IT). However, there are some standards here, I believe that all engineers will agree. Software Disresses (although) Fulfills ITS Requirements, because (yet) exhibits some or all of the following three characteristics, it is "bad design". 1. It is difficult to change because each change affects too much of the system. (Rigidity rigidity, stiffness). 2. When you make a change, the unnecessary part will be wrong. (Fragility, Fragia) 3. It is difficult to reuse in another application because it cannot be separated from the current application. (Immobility, fixed, non-transplantability) In addition, it is difficult to disconstrate, and it is a flexible, robust (Robust) and can be restored. Reusable and meet its needs, it will be a "bad design". Therefore, we can use these three characteristics as a way to determine a design "good" or "bad". Causes of "bad design"
What is the design of rigidity (ragile), fragile, is not easy to transplantation (IMMOBILE) It (why) is the interdependnce of the module in this design. This design is rigid. If it cannot be easily changed, such rigidity is because of this fact - for severe interdependent software, a single change caused cascading variation in the dependent module (a Cascade of Changes, chain reaction ). Once the range of cascading can not be known in advance, the impact of changes cannot be estimated. This leads to changes in the overhead that cannot be predicted. The management level is so unpredictable and becomes unwilling to approve changes. Therefore, it is a rigidity in the design to officially (official, OFFICIALLY). Vulnerability (fragile, fragility) is a trend in which procedures are interrupted in many places when a single change occurs. Often, new problems appear in places where there is no conceptual relationship with the changed area. Such fragile properties greatly reduce the design of credibility and maintenance orGanization. Users and managers cannot predict the quality of their products. A simple change of a part of the application has caused failure to fail at other parts that look completely. Solve those problems leading to even more problems, while maintenance work (become) like a dog chasing it tail. The design is not easy to transplant, which means that the desired part is highly dependent on the details of the less desired. Some designers assigned tasks are research (survey) design to see if it can be multiplexed in different applications, they will be amazed in new applications (Designers Tasked with Investigating the) Design To See If IT Can Be Reused With How Well The Design Would Do In The New Application.) However, if a design is highly dependent on each other, they will be very distressed and put this design. The desired part is separated from the parts that do not want in the design, and there must be a lot of work. In most cases, such a design is not reused because the separation cost is considered higher than the cost of Redevelopment of the Design. Example: "Copy" program
A simple example can help understand this. Consider a simple program that assumes the following tasks: The characters entered on the COPY keyboard are output on the printer. Assuming that the implementation platform does not have an operating system to support the device independence. We can imagine that the structure of this program is like Figure 1. Figure 1 is a "structure chart" [1]. (1.se: The Practical Guide To Structured Systems Design, by meilir page-Jones, YourDon Press, 1988) It indicates that there are 3 modules or subroutines in the app. The Copy module calls the other two modules. People are easily imagined, there is a loop in the Copy module (see Listing 1.). This cycle calls the Read Keyboard module to get a character from the keyboard, and then send the character to the Write Printer module to print the character.
Listing 1: "Replication" program
Void copy () {
INT C;
While ((c = readkeyboard ())! = EOF) Writeprinter (c);
}
These two low-level modules have good reuse. They can be used in many other programs to access keyboards and printers. This is the same as the reuse of our subroutine. However, in any context of the keyboard or the printer, the "Copy" module is not available. What is red is that the system's intelligence relies on this module. It is a very interesting policy we want to reuse (POLICY, Function).
For example, a new program that copies the characters entered by the keyboard to disk files. Obviously, we want to reuse the Copy module because it encapsulates the high-level features we need, that is, it knows how to copy characters from a source to one receiver. Unfortunately, the COPY module depends on the Write, the Printer module, so it cannot be reused in the new context. Of course, we can modify the COPY module to give it a new desired functionality (see Listing 2).
Listing 2: "Enhanced" "Replication" program
Enum outputDevice {printer, disk};
Void Copy (OutputDevice dev) {
INT C;
While ((c = readkeyboard ())! = EOF)
IF (dev == printer)
Writeprinter (c);
Else
WriteDisk (c);
}
We add an IF statement that rely on some mark, and make a choice between Write Printer modules and Write Disk modules. However, this adds new dependencies to the system. Over time, more and more devices must be added to the COPY program, which stuffed with the IF / ELSE statement and will rely on a lot of lower-layer modules. In the end it became hard.
["Example" content, it seems that there is no mistake, but the error will appear when you have no obvious feelings.
Point 1. Mixed high-level - low-Low Level Modules. When describing the principle of abstract type, we pointed out that the customer client should not rely on specific class Server, which should rely on abstract type IServer. Different authors can replace customers client in various nouns. If you discuss issues in the framework, use the control module; regardless of the hierarchical occasion, in the same layer, you can use Client-iServer to perform a general discussion. Robert C. Martin uses a high-level-low-level module. If it is wrong, there is a suspicion of the phrase. However, in advance, readers should be bought, and there is a clear upper-lower-layer module in the hierarchical architecture, and the framework belongs to the lower layer. The application belongs to the upper layer. Robert C. Martin's high-level - low-level module, now you can equivore C-S, which will also be equivalent to the upper layer. This meaningless term is most suitable for the water to touch the fish.
His example, in a clear language, is that it is not good if the CLIENT Coyp relies on specific Server, ie Write, Printe is not good. 】
§3 relying on invert
One way to portray the above problem is to pay attention to modules containing high-level functions (such as COPY () modules) depend on the low-level more detail it controls (for example,. Writeprinter () and readkeyboard ()). If we can find a new way to make the copy () module does not depend on the details it control, then we can freely reuse it. We can develop other programs where using this module to copy characters from any input device to any output device. OOD gave us a mechanism to achieve this dependency inversion. Listing 3: Object-oriented "replication" program
Class reader {
PUBLIC:
Virtual int = 0;
}
Class Writer {
PUBLIC:
Virtual Void Write (char) = 0;
}
Void Copy (Reader & r, Writer & w)
{
INT C;
While (c = r.read ())! = EOF)
w.write (c);
}
}
At this point, the COPY class is neither rely on Keyboard Reader and does not depend on Printer Writer. Therefore, dependence has been inverted; the COPY class depends on abstraction (abstract class), while specific readers and writers depend on the same abstraction. Now, we can multiplex Copy, which does not rely on "Keyboard Reader" and "Printer Writer". We can invent all new Reader and Writer's creatures to support our COPY class. What is more, no matter how many (specific) readers and writers are created, the COPY class will not rely on any of them. There is no interdependence here to cause the design to become rigid or fragile, and COPY () [function? 】 It can be used in many different contexts. It is portable.
[Total 2. Strong to reverse. Robert C. Martin has three meanings in Inversion / reverse / inverted. Here is the first one.
He said, "Copy () module, it contains high-level strategy, depending on its underlying detailed module", when Client relies on abstract type iServer, he said that the Copy class does not rely on "Keyboard Reader" and "Printer Writer", Therefore, dependence is reversed!
Client has become a so-called "reverse" No. 1 from relying on specific class Server to relying on iServer. 】
Detective independence
Here, maybe some people muttered, I can use COPY () functions to achieve the same effect by using Stdio.h inherent equipment independence (ie, getChar and Putchar), with C to achieve the same effect (see Listing 4).
Listing 4: Use stdio.h's "replication" program
#include
Void copy () {
INT C;
While ((c = getchar ())! = EOF)
PUTCHAR (C);
}
If you carefully consider Listing 3 and 4, you will realize that both are logical equivalents. The abstraction class in Figure 3 is replaced with another different abstraction in the list 4. Indeed, in Listing 4 does not use class and pure virtual functions, but it still uses abstraction and polymorphism to achieve the purpose [呵, C's abstraction and polymorphism! ! ! . Moreover, it still uses relying on the inversion! In Listing 4, the COPY program does not rely on any detail whose control, the opposite depends on abstract devices declared in stdio.h. Moreover, the IO device that is finally called also depends on abstraction declared in stdio.h. Therefore, the independence of the equipment in the stdio.h library is dependent on the other example. Since we have seen some examples, we can explain the general form of DIP.
§4 Relying on the principle of inversion
A. The high-level module should not rely on the low-level module. Both should depend on abstraction.
B. Abstract should not depend on the details. Details should depend on abstraction.
[It is because of the "reverse" No. 1, many people think that the version of the fancy version of the interface programming / abstract dependent principle
You have to pay attention to some words appear in his text: it is controlled, inversion / reverse / inversion. This is preparing for "reversing" No. 3]
Some people may ask why I want to use the word "invert" ("inversion".). Frankly, this is because of comparing traditional software development methods - such as structured analysis and design, tends to create such software structures: high-level modules depends on low-level modules and abstract dependent details. Indeed, one of these methods is to define a subroutine hierarchy to describe how the high-level module calls the low-level module, and Figure 1 is a good example of such levels. Therefore, a dependency-oriented program-oriented program-oriented program, corresponding to the conventional process-oriented method, is typically formed, is "inverted".
[Some people may ask, why I want to use the word "invert" ("inversion".). Frankly, I want to introduce IOC. Robert C. Martin will vulnerability when explaining inutance. "Reverse" 2: Inversion to the traditional software development method.
This kind of compament of bad C language designs and good Java language (design well-oriented program-oriented program) is compared and considered to be 2nd, which is obviously extremely ridiculous. If someone compares the good people in the woman and the bad guys in the man, it is conclusively: women and men are inversely inhabitant, which is obviously extremely ridiculous.
Relying on the principle of abstract types, is not using the "traditional software development method", "process method" or "object-oriented", and there is no relationship.
If all Java programmers are used to relying on abstract types, or everyone has relying on abstract type, then Robert C. Martin will never appear, and it is not necessary for interface programming to give Java programmers. brainwashing.
It is because DIP's inverted No. 2, it is a crap, so his advocacy has to play, such as "Head First Design Mode" to reliatory explanation or even "invert your thinking method."
】
Think about the meaning of the high-level module dependent on the low-level module. It is a high-level module that includes an important policy decision and business model that includes the application of these modules. However, when these modules rely on lower-level modules, the change in low-level modules has a direct impact on them and forced them to change.
[In the subconscious of Robert C. Martin, the underlying module is specific. It seems that all underlying modules are Server, waiting for DIP to save]
This kind of dilemma is ridiculous! The high-level module should force the low-level module to change. It is a high-level module to be better than the low-level module. In any case, the high-level module should not depend on the low-level module. Moreover, the high-level module is what we want to be reused. We are already very good at multiplexing low-level modules in the form of sub-library. When the high-level module rely on a low-level module, it is very difficult to reuse those high-level modules in different context. On the other hand, when the high-level module does not depend on the low-level module, the high-level module can be readily reused.
It is this principle that it is the core of framework design (Framework design.).
[Robert C. martin For its principle, it is presented in the premise, and the high-level should be designed as a framework.
But Robert C. Martin is given by the example, the Copy module, the Java programmer knows that there are almost no one to develop its own IO framework for specific applications, but use Java's IO toolbox to construct a wide range of applications. In many cases, the so-called high-level modules usually do not have multiplexedity.
In the framework, the Sorttest → INTSORT example, the high-level module Sorttest does have the possibility of the framework; whether in the application, or the framework, possible design is Sorttest → IntSort. There is no change in dependency. 】
Layering
According to Booch, "all well structured object-oriented architecture has defined levels, through {THOUGH, it should be THROUGH] to define a good and controlled interface (Interface) to make each Layers provide some relevant services. " Figure 3: Simple layered
[Again again: In the subconscious of Robert C. Martin, the underlying module is specific. So what he said here is nonsense!
The front reversal, he also felt embarrassed. Thus, he needs to put his inversion dummy into the inversion / IOC in the heart in the hierarchical structure. This is what he really wants to express, but he only said. I will make it up, called "reverse" No. 3.
】
A naive (naive) explained this sentence can cause the designer to make a structure similar to Figure 3. In this illustration, high-level POLICY uses a lower Mechanism (mechanism); (the latter) sequentially uses a detail of the layer UTILITY (Tool) class. This situation may seem proper, but there is a change in the Insidious Characteristic - POLICY layer is sensitive to all modifications in the UTILITY layer. (Because) dependent is deliverable. The Policy layer relies on some things, and some things rely on the Utility layer, so the policy of policy relies on the Utility layer, which is very unfortunate.
Figure 4 shows a more suitable model. Each lower level layer is described by an abstract class, while the actual layer is derived from these abstractions. Each higher level class uses the next lower level by this abstract interface. Therefore, each layer does not rely on any other layer. Instead, the layer relies on abstract classes. Not only the policy layer is disconnected from the transfer property of the Utility layer, but even the directy layer directly dependent on the Mechanism layer is also disconnected.
With this model, any variation of the Mechanism layer or UTILITY layer does not affect the Policy layer, and for any context of the low-level module that conforms to the Mechanism layer interface, the Policy layer can be multiplexed. Therefore, by inverting dependence, we have built a more flexible, more durable, more portable. Interface in C is separated from the implementation phase
Now, in the DIP of Robert C. Martin, there is still something to introduce, what is an abstract-detail correspondence? Since the first of the DIP has explained that the low-level module (subtype) is dependent on abstraction, why should I still explain that "Abstract should not depend on the details, the details should depend on abstraction"? 】
Some people will complain, the structure in Figure 3 does not exist that I claim to dependence and transfer dependence, no matter how, the Policy layer relies only to the interface of the Mechanism layer. How does the implementation of the implementation of the MECHANISM layer will eventually affect the Policy layer?
This may be true in some object-oriented languages. In those languages, the interface automatically separated from the realization. However, in C , the interfaces and implementations do not have separation. Accurately, C , separation is the definition of the class and the definition of its member function.
In C , we usually divide a class into two, a.h, and a.cc; .h file contains the definition of the class. The CC file contains the definition of the members of the class. Definition of class - In .h file, all member functions of all classes and all members variables are included. This information is outside the simple interface. All functional functions required for classes are also declared in .h files. These features and private variables are part of the implementation of classes, and the modules they appear are in this class must be dependent. Therefore, in C , the implementation is not automatically separated from the interface.
In C , the interface is derived from the defective lack of phase separation, and can be handled using a pure abstractrat class. The pure abstract class is such a class that does nothing except that it contains a pure virtual function. Such classes are pure interfaces and it's .h file does not include implementation. Figure 4 shows this structure. The abstraction of the abstraction in Figure 4 is pure abstract (class) such that each layer is only dependent on the interface of the sublayer.
[Robert C. Martin hopes to use its own terminology - detail, replace "interface programming, rather than programming", just as abstract type-specific type replacement interfaces in abstract dependent principles - implementation. Abstract dependent principles are replaced because this interface - implementation, easy to confuse the interface in the principle of Parnas, i.e., interface and implementation.
Parnas Principle Description Users only need to understand the interface, so it can be considered vulnerable to the implementation. The Java abstraction method and the pure virtual function of C can define a simple interface. Robert C. Martin's understanding of the Parnas principles is the interface and implementation of physically separated. Therefore, in the "Interface in C and Implementation" section, Write: "C , the implementation is not automatically separated from the interface. ... C , the interface is dealing with the lack of the phase separation, and can be processed using a pure abstractract class. In other words, only Java interface (Java8) can meet his interface and achieve separation. Obviously, he doesn't know what interface is separated from the implementation. 】
§ A simple example
No matter where it is, a class can be applied to another time to send a message to another. For example, consider the case of the Button object and the LAMP object.
Button objects can sense external environments. It can determine that the user is already "press" or does not press it. This perceived mechanism is not important. It may be a Button icon on the GUI, or the physical button pressed by the person's finger, even the motion detector of the home security system. Button object detects whether the user activates it or fail to fail (on or off). Lamp (Light) Object Acceptance (Affects) External Environment (Stimulation). Once the Turnon message is accepted, the lamp issues some form of light. When the Turnoff is accepted, the light is extinguished. Its physical mechanism is not important, it may be an LED on the computer console, a Mercury Vapor Lamp, or a laser printer of a laser printer.
How do we design a system to control the Button object to control the LAMP object? Figure 5 shows a naive model. The Button object is simple to send the Turnon and Turnoff messages to the LAMP object. Convenience, the Button class uses the "included" relationship to have an instance of a LAMP class. Listing 5 shows the C code of this model.
Figure 5: Childish button / lamp model
[This simple example does not need to be diagrams, do not need to look carefully to his source code. The naive model is Button → Lamp. 】
Listing 5: Childish button / lamp code
--------------------------------
Class lamp {
PUBLIC:
Void turnon ();
Void turnoff ();
}
------------- Button.h ---------------
Class lampp;
Class button {
PUBLIC:
Button (Lamp & L): ITSLAMP (& l) {}
Void detect ();
Private:
Lamp * itslamp;
}
------------- Button.cc --------------
#include "button.h"
#include "lamp.h"
Void button :: detect () {
Bool Button = getPhysicalState ();
IF (Buttonon)
ITSLAMP-> Turnon ();
Else
ITSLAMP-> Turnoff ();
}
Note: The Button class is directly dependent on the lamp class. In fact, Button.cc file #include has a lamp.h file. This dependence implies, regardless of when the LAMP class changes, the Button class must change, or at least recompile. Moreover, multiple BUTTON categories can operate motor objects. Figure 5 and List 5 violate the dependency inversion principle. The high-level (polic) is not separated from the low-level module; abstraction is not separated from detail. There is no such separation, the high-level automatic relies on the low-level module and is abstract and automatically dependent on the details. Discover the potential abstraction
What is a high level of strategy? It is the basis of abstraction as an application, and the principle is not changed without the change of details. In the example of Button / Lamp, potential abstraction is an ON / OFF signal that identifies the user and passes this signal to the target object. What is the mechanism of identifying user signals? Tube! What is the target object? Tube! These are all not affecting the abstract branches.
In order to match the dependency inversion principle, we must isolate this abstraction with the details of the problem. Therefore, we must guide the dependence design to let the details depend on abstraction. Figure 6 shows this design.
[However, the interface and implementation are not the main points they want. "Abstraction is not separated from the details. There is no such separation ... and abstraction is also automatically dependent on the details ... In order to match the inverted principle, we must isolate this abstraction with the details of the problem. Therefore, we must guide the dependence design to let the details depend on abstraction. "Etc., but it means that it is necessary to depend on Button → Lamp, change to button => ibbutton → iButtonClient <= LAMP. Finally, his abstraction - details are equivalent to abstract type - implementation equivalents.
what do you want to say in the end? If abstract - details are iServer and Server, iServer cannot rely on Server, like you say "Water should not go up", the water has never flowed up!
The only advantage is that abstract = a, detail = B, this is "A should not rely on B, B should rely on A", perfect dependence inversion - although never exist.
If you say directly to the child-dependent type, then he still plays a fart. This is the true meaning of the principles B, a simple manner, can pack the principle of many people's brain holes. Some people have started to play: abstract is stable ...]
In Figure 6, we have isolated the abstraction of the Button class from its detailed implementation. Listing 6 gives the corresponding code. Note: High-level Policy Full Packages (Capture) In the abstract Button class, the Button class is completely unknown to the physical machinery of detecting user signals; it is nothing to know. These details are isolated into the specific sect: ButtonImplementation and Lamp.
Listing 6 High-level policies can be multiplexed to any button, as well as any type of device that requires control. Moreover, it is not affected by changes in low-level mechanisms. Therefore, it is robust, flexible, and can be used in front of the change.
---------- BYTTONCLIENT.H ---------
Class ButtonClient
{
PUBLIC:
Virtual void turnon () = 0;
Virtual void turnoff () = 0;
}
----------- Button.h ---------------
Class buttonclient;
Class button
{
PUBLIC:
Button (ButtonClient &);
Void detect (); Virtual bool getState () = 0;
Private:
ButtonClient * itsclient;
}
--------- Button.cc ----------------
#include button.h
#include buttonclient.h
Button :: Button (ButtonClient & BC)
: ItsClient (& BC) {}
Void Button :: Detect ()
{
Bool buttonon = getState ();
IF (Buttonon)
ITSCLIENT-> Turnon ();
Else
ITSCLIENT-> Turnoff ();
}
----------- Lamp.h ----------------
Class Lamp: Public ButtonClient
{
PUBLIC:
Virtual void qurnon ();
Virtual void turnoff ();
}
--------- Buttonimp.h -------------
Class ButtonImplementation
: Public Button
{
PUBLIC:
ButtonImplementaton
ButtonClient &);
Virtual bool getState ();
}
Abstract further expansion
People (Once? Ones) can make normal complaints for the design of Figure / Listing 6. Devices controlled by Button must be derived from ButtonClient. If the Lamp class comes from a third-party library, and we cannot modify the source code.
Figure 7: LAMP adapter
Figure 7 shows how to connect third-party LAMP objects to the module using adapter mode. The LampAdapter class simply converts the Turnon and Turnoff messages inherited from ButtonClient to the Lamp class to understand those messages.
in conclusion
Dependency inverting principle is the root cause of many superiority declared by object-oriented technology. The appropriate application is necessary to create a reuse framework. It is extremely important to construct a change in flexible code. Since abstract and detail are completely isolated from each other, the code is very easy to maintain.
This article is my new book - "Mode and Object-Oriented Design Advanced Principles" will soon be published by the Prentice Hall - the highly concentrated version of the next chapter. In the following article, we will explore object-oriented design many other principles. We will also learn a variety of design patterns, as well as the powerful or weakness they contact to C implementation. We will learn the class categories of Booch in C , which is the applicability of the C Namespaces. We will also define what is "cohesion" and "coupling" and we will develop a methodology (Metrics) that measures object-oriented design quality. Since then, we will discuss many other topics that are interested in.