Understanding JavaScript series (22) : S O. L. I. D DIP dependency inversion principle explanation of the five principles

  • 2020-05-10 17:42:27
  • OfStack

preface

In this chapter we will interpret the S. O. L. I. D5 big principle JavaScript language implementation of article 5, dependency inversion principle LSP (The Dependency Inversion Principle).

English text: http: / / freshbrewedcode com/derekgreer / 2012/01/22 / solid - javascript - the - dependency - inversion - principle /
Dependency inversion principle

The dependency inversion principle is described as:

A. High-level modules should not depend on low-level modules.   Both should depend on abstractions.
    high level modules should not depend on low level modules, both should rely on abstractions

B. Abstractions should not depend upon details.   Details should depend upon abstractions.
    abstractions should not depend on details, details should depend on abstractions
The most important issue in relying on the inversion principle is to ensure that the primary components of the application or framework are decoupled from the non-critical implementation details of the underlying components, which ensures that the most important parts of the program are not affected by changes to low-level components.

Part 1 of this principle is about the way in which high-level modules (which encapsulate the core business logic of a program) are coupled to low-level modules (which are the base points) in a traditional split architecture. When the dependency inversion principle is applied, the relationship is reversed. Unlike high-level modules that depend on low-level modules, dependency inversion makes low-level modules depend on interfaces defined in high-level modules. For example, if you want to persist the data to the program, the traditional design is the core module relies on a persistent API module, and according to the dependency inversion principle after reconstruction, is the core module need to define a persistent API interface, and then the realization of the persistence of instance this API interfaces need to implement the core module definition.

Part 2 of the principle describes the correct relationship between abstraction and detail. To understand this part 1, it is helpful to know C++ language, because its applicability is obvious.

Unlike statically typed languages, C++ does not provide a language-level concept for defining interfaces, and what exactly is going on between class definitions and class implementations. In C++, classes are defined in the form of header files that define the class member methods and variables that the source files need to implement. Because all variables and private methods are defined in the header file, they can be abstracted to decouple them from the implementation details. The concept of an interface is implemented by implementing classes by defining only abstract methods (C++ is the abstract base class).

DIP and JavaScript

Because JavaScript is a dynamic language, there is no need to abstract for decoupling. So the abstraction should not depend on the details and this change in JavaScript doesn't have much of an impact, but the higher level modules should not depend on the lower level modules but it does have a big impact.

When discussing the dependency inversion principle in the context of statically typed languages, the concepts of coupling include semantics (semantic) and physics (physical). That is, if a high-level module depends on a low-level module, it is coupling not only the semantic interface, but also the physical interface defined in the underlying module. That is to say, high-level modules need to be decoupled not only from the 3rd class library, but also from the native low-level modules.

To illustrate this point, imagine that a.NET program might contain a very useful high-level module that relies on a low-level persistence module. When the author needs to add a similar interface to persistence API, whether or not the dependency inversion principle is used, a high-level module cannot be reused in another program without re-implementing the new interface of the low-level module.

In JavaScript, the applicability of the dependency inversion principle is limited only to the semantic coupling between high-level and low-level modules; for example, DIP can add interfaces as needed rather than coupling the implicit interfaces defined by low-level modules.

To understand this, let's look at the following example:


$.fn.trackMap = function(options) {
    var defaults = {
        /* defaults */
    };
    options = $.extend({}, defaults, options);     var mapOptions = {
        center: new google.maps.LatLng(options.latitude,options.longitude),
        zoom: 12,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    },
        map = new google.maps.Map(this[0], mapOptions),
        pos = new google.maps.LatLng(options.latitude,options.longitude);     var marker = new google.maps.Marker({
        position: pos,
        title: options.title,
        icon: options.icon
    });     marker.setMap(map);     options.feed.update(function(latitude, longitude) {
        marker.setMap(null);
        var newLatLng = new google.maps.LatLng(latitude, longitude);
        marker.position = newLatLng;
        marker.setMap(map);
        map.setCenter(newLatLng);
    });     return this;
}; var updater = (function() {
    // private properties     return {
        update: function(callback) {
            updateMap = callback;
        }
    };
})(); $("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater
});

In the code above, there is a small JS class library that converts one DIV to Map to display the location of the current trace. The trackMap function has two dependencies: Google Maps API and Location feed on the third side. The responsibility of the feed object is to call an callback callback (provided at initialization time) when the icon location is updated and pass in the latitude latitude and precision longitude. Google Maps API is used to render the interface.

The interface of the feed object may or may not be designed to fit the trackMap function. In fact, its role is simple, focusing on simple different implementations and not so dependent on Google Maps. Since trackMap is semantically coupled to Google Maps API, the trackMap function will have to be rewritten to accommodate different provider if you need to switch between different map providers.

In order to reverse the semantic coupling of Google maps class library, we need to rewrite the trackMap function to semantically couple an implicit interface (abstracted from the map provider provider's interface) and an implementation object to adapt Google Maps API. The following is the reconstructed trackMap function:


$.fn.trackMap = function(options) {
    var defaults = {
        /* defaults */
    };     options = $.extend({}, defaults, options);     options.provider.showMap(
        this[0],
        options.latitude,
        options.longitude,
        options.icon,
        options.title);     options.feed.update(function(latitude, longitude) {
        options.provider.updateMap(latitude, longitude);
    });     return this;
}; $("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater,
    provider: trackMap.googleMapsProvider
});

In this release, we redesigned the trackMap function and the required map provider interface, then moved the implementation details to a separate googleMapsProvider component, which might be packaged as a separate JavaScript module. The following is my googleMapsProvider implementation:

trackMap.googleMapsProvider = (function() {
    var marker, map;     return {
        showMap: function(element, latitude, longitude, icon, title) {
            var mapOptions = {
                center: new google.maps.LatLng(latitude, longitude),
                zoom: 12,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            },
                pos = new google.maps.LatLng(latitude, longitude);             map = new google.maps.Map(element, mapOptions);             marker = new google.maps.Marker({
                position: pos,
                title: title,
                icon: icon
            });             marker.setMap(map);
        },
        updateMap: function(latitude, longitude) {
            marker.setMap(null);
            var newLatLng = new google.maps.LatLng(latitude,longitude);
            marker.position = newLatLng;
            marker.setMap(map);
            map.setCenter(newLatLng);
        }
    };
})();

After making these changes, the trackMap function will become very flexible. Instead of relying on Google Maps API, the trackMap function can arbitrarily replace other map providers, which means that any map provider can be adapted according to the requirements of the program.

When dependency injection?

In fact, the concept of dependency injection is often confused with the dependency inversion principle. To clarify the difference, it is necessary to explain 1:

Dependency injection is a special form of inversion of control, which means how a component acquires its dependencies. Dependency injection means that a dependency is provided to a component rather than being acquired by the component. It means that an instance of a dependency is created, the dependency is requested through the factory, and the dependency is requested through Service Locator or the initialization of the component itself. Both the dependency inversion principle and dependency injection focus on dependencies and are used for inversion. However, the dependency inversion principle does not focus on how components acquire dependencies, but only on how high-level modules are decoupled from low-level modules. In a sense, the dependency inversion principle is another form of inversion of control, where the module is reversed to define the interface (defined from the low level, reversed to the high level).

conclusion

This is the last article of the five principles, in which we see how SOLID is implemented in JavaScript, different principles are explained in JavaScript from different angles. (note: in fact, the uncle thinks that although it is a little bit of the same, but from another level, the general principle in all languages is the same.)


Related articles: