[11] dip: Dependency inversion principle

Source: Internet
Author: User

Agile Software development principles, models and practices (C # Edition)

Chapter 2Dip: Dependency inversion principle

Traditional software development methods, such as structural analysis and design, always tend to create software structures where some high-level modules depend on low-level modules and policies depend on details. In fact, one of the purposes of these methods is to define sub-ProgramA hierarchy that describes how a high-level module calls a lower-level module. However, for a well-designed object-oriented program, its dependency program structure is "Inverted" compared with the conventional structure of traditional procedural method design.

Consider what the high-level module of the lower-level module means. It is precisely the high-level module that contains the important policy selection and business model in the application. These high-level modules differentiate their applications from others. However, if these high-level modules depend on the lower-level modules, changes to the lower-level modules will directly affect the higher-level modules, forcing them to make changes in turn.

This situation is ridiculous! This should be because the policy setting module of the higher level affects the detailed implementation module of the lower layer. Modules that contain high-level business rules should take precedence over those that contain implementation details. In any case, the upper-level modules should not depend on the lower-level modules.

In addition, what we want to be able to reuse is the high-level policy setting module. We are already very good at reusing lower-layer modules in the form of libraries. If a high-level module is independent of a low-level module, it becomes very difficult to reuse the High-Level module in an inaccessible context. However, if a high-level module is independent of a low-level module, it can be easily reused. This principle is the core principle of framework design.

Definition

"A. High-level modules should not depend on low-level modules. Both of them should depend on abstraction."

"B. abstraction should not depend on details. Details should depend on abstraction."

Hierarchical

Booch once said: "All well-structured object-oriented architectures have clear hierarchical definitions, and each layer provides services in a group through a well-defined and controlled interface ." A simple understanding of this statement may result in a similar structure in the design (Figure 1-1. As shown in the figure, the policy layer of the higher layer uses the lower-layer mechanism layer, while the mechanism layer uses the more detailed utility layer. This seems to be correct, but it has a hidden false feature, that is, the polity layer is sensitive to changes to the bottom to the utility layer.Dependencies are passed.. The policy layer depends on some levels that depend on the utility layer: Therefore, the policy layer is passed on the utility layer. This is a very slot.

Figure 1-1 simple hierarchical Solution

(Figure 1-2) shows a more appropriate model. Each high-level layer declares an abstract interface for the required service. These abstract interfaces are implemented at a lower level. Each high-level class uses the next layer through this abstract interface. In this way, the upper layer does not depend on the lower layer. The lower layer depends on the abstract service interface declared in the higher layer. This not only removes the dependency between policylayer and utilitylayer, but also removes the dependency between policylayer and the Mechanism layer.

 

Figure 1-2 inverted hierarchy

Inverted interface ownership

Please note that the inversion here is not only the inversion of dependency, but also the inversion of interface ownership. We usually think that tool libraries should have their own interfaces. However, when dip is applied, we find that customers often have abstract interfaces, and their service providers derive from these abstract interfaces.

This is the famous Hollywood principle: "Don't call us, we'll call you. (Don't call us, I will call you .) "The lower-level module implements the interface declared in the higher-level module and called by the higher-level module.

Using this inverted interface ownership, any changes made to the mechanical layer or utilitylayer will no longer affect the policylayer. In addition, policylayer can be reused in any context that defines compliance with policyservice. In this way, by reversing these dependencies, we have created a more flexible, persistent, and easy-to-change structure.

The ownership mentioned here only means that the interface is released with the client program that owns them, rather than the server program that implements them. The interface and the customer are in the same package or library. This forces the server program or package to depend on the client library or package.

Of course, sometimes we don't want the server program to depend on the client program, especially when there are multiple client programs but only one server, in this case, the customer program must follow the service interface and publish it to an independent package.

Dependent on abstraction

A slightly simple but still very effective interpretation of DIP is such a simple heuristic rule: "dependent on abstraction. "This is a simple statement. It is recommended that the heuristic rule do not depend on a specific class. That is to say, all dependencies in the program should end with abstract classes or interfaces.

A. No variable should hold a reference pointing to a specific class.

B. No class should be derived from a specific class.

C. Any method should not overwrite the implemented methods in any of its base classes.

Of course, every program will violate this inspiration rule. Sometimes you must create instances of specific classes, and the modules that create these instances will depend on them. In addition, this heuristic rule does not seem reasonable for classes that are specific but stable. If a specific class does not change much and other similar Derived classes are not created, dependent on it will not cause damage.

For example, in most systems, the classes that describe strings are specific. For example, in C #, the string class is string. This class is stable, that is, it will not change. Therefore, directly relying on it will not cause damage.

However, most of the classes we write in the application are unstable. We don't want to rely on these unstable classes directly. By hiding them behind abstract interfaces, they can be isolated from instability.

This is not a complete solution. Often, if an interface of an unstable class must change, this change will definitely affect the abstract interface that represents the class. This change undermines the isolation maintained by abstraction.

From this we can see that the heuristic rule is a bit simple to think about. On the other hand, if you think that the service interfaces they need are declared by the customer module or layer, then only whenCustomerThe interface must be changed. In this way, changing the class that implements the abstract interface will not affect the customer.

Simple dip example

Dependency inversion can be applied to any place where one class sends messages to another class. For example, between a button object and a lamp object.

The button object perceives changes in the external environment. When a poll message is received, it determines whether the message is "pressed" by the user. It does not care about the mechanisms it uses to perceive. It may be a button icon on the GUI, a real button that can be pressed with your fingers, or even a motion detector in the home security system. The button object can be detected by the user to activate or disable it.

The lamp object affects the external environment. When a turnon message is received, it displays a certain light. When a turnoff message is received, it turns out the light. The specific physical mechanism is not important. It can be a computer console led, a silver-water lamp in a parking lot, or even a laser in a laser printer.

How can we design a system that uses the button object to control the lamp object? (Figure 1-3) shows an immature design. The button object receives the poll message, determines whether the button is pressed, and then simply sends the turnon or turnoff message to the lamp object.

 

Figure 1-3 immature button and lamp Models

Why is it immature? Consider the C # corresponding to this model #Code(Code List 1-1 ). Note that the button class directly depends on the lamp class. This dependency means that when the lamp class changes, the button class will be affected. In addition, it is impossible to reuse a button to control a motor object. In this design, the button controls the lamp object and can only control the lamp object.

Code List 1-1 button. CS

Public   Class Button
{
Private Lamp alamp;

Public VoidPoll ()
{
If(/*Some condition*/)
{
Alamp. trunon ();
}
}
}

 

This scheme violates dip. The application's high-level policies are not separated from those of the lower layer. Abstraction is not separated from details. Without such separation, the high-level policy is automatically dependent on the lower-layer module, and the abstraction is automatically dependent on the specific details.

Find potential abstractions

What is a high-level policy? It is the abstraction behind the application and the truth that does not change with the specific details. It is a system inside the system-it is a metaphor (metaphore ). In the example of Button/lamp, the abstraction behind it is to detect the user's on/off commands and send the commands to the target object. What mechanism is used to detect USER commands? It doesn't matter! What is the target object? It's equally irrelevant! These will not affect the specific abstract details.

By reversing the dependencies between lamp objects, you can improve the design in Figure 1-3. In (Figure 1-4), we can see that the button is now associated with an interface called buttonserver. The button can be used to enable or disable something. Lamp implements the buttonserver interface. In this way, lamp is dependent on other things, rather than being depended on.

 

Figure 1-4 Dependency inversion principle for lamp applications

The design in (Figure 1-4) allows the button to control any device that is willing to implement the buttonserver interface. This gives us great flexibility. It also means that the button object can control the objects that have not been created.

However, this scheme imposes a constraint on the objects that need to be controlled by the button. The object to be controlled by the button must implement the buttonserver interface. This is not good because these objects may also be controlled by switch objects or some objects different from button objects.

By reversing the dependency direction and making lamp dependent on other classes rather than other classes, we have made lamp dependent on a different specific detail: button. Have we already done this?

Lamp does depend on buttonserver, but buttonserver does not depend on button. Any object that knows how to operate the buttonserver interface can control lamp. Therefore, this dependency is only dependent on the name. You can fix this by giving the buttonserver a more common name, such as switchabledevice. You can also ensure that the button and switchabledevice are placed in different libraries, so that the use of the switchabledevice does not need to include the use of the button.

In this example, the interface has no owner. This is an interesting situation where interfaces can be used by many different customers and implemented by many different service providers. In this way, the interface must exist independently and not belong to any party. In C #, you can put it in a separate namespace and library.

Furnace example

Let's look at a more interesting example. Consider the software for controlling furnace regulators. The software can read the current temperature from one I/O channel and instruct the furnace to open or close by sending commands to another I/O channel.AlgorithmThe structure looks like code 1-2.

Code List 1-2 simple algorithm for Temperature Regulator

Const   Byte Thermoneter = 0x86 ;
Const   Byte Furnace = 0x87 ;
Const   Byte Engage = 1 ;
Const   Byte Disengage = 0 ;

Void Regulate ( Double Mintemp, Double Maxtemp)
{
For (;;)
{
While ( In (Thermoneter) > Mintemp)
Wait ( 1 );
Out (Furnace, engage );

While ( In (Thermoneter) < Maxtemp)
Wait ( 1 );
Out (Furnace, disengage );
}
}

 

The high-level intent of an algorithm is clear, but the implementation code contains many lower-layer details. This Code cannot be reused for different control hardware at all.

Because there are few codes, doing so will not cause too much damage. However, even so, it is a pity that the algorithm will lose its reusability. We prefer to put this dependency upside down, as shown in figure 1-5.

 

Figure 1-5 General Regulator

The figure shows that the regulate function accepts two interface parameters. The thermometer interface can be read, while the heater interface can be started and stopped. This is what the regulate algorithm needs. The implementation code is shown in Listing 1-3.

This leads to an inversion of dependency, so that high-level adjustment policies no longer rely on specific details of any thermometer or furnace. This algorithm is highly reusable.

Code List 1-3 common regulators

Void Regulate (Thermometer T, heater H, Double Mintemp,
Double Maxtemp)
{
For (;;)
{
While (T. Read () > Mintemp)
Wait ( 1 );
H. engate ();

While(T. Read ()<Maxtemp)
Wait (1);
H. Disengage ();
}
}

 

Conclusion

In the traditional process, the dependency structure created by the program design depends on the details. This is terrible, because it will affect the policy by changing the details. Object-Oriented Programming reverses the dependency structure, so that the details and policies depend on abstraction, and often the customer program has service interfaces.

In fact, the inversion of this dependency is exactly the sign of a good object-oriented design. It is irrelevant to the language used for programming. If the program dependency is inverted, it is an object-oriented design. If the program dependency is not inverted, it is a procedural design. :-)

The dependency inversion principle is a basic low-layer mechanism that implements the benefits declared by many object-oriented technologies. Its correct application is required for creating reusable frameworks. At the same time, it is also very important for building flexible code in the face of changes. Since abstraction and details are isolated from each other, the code is very easy to maintain.

 

End.

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.