Home > Article > Web Front-end > In-depth understanding of JavaScript series (22): Detailed Explanation of Dependency Inversion Principle DIP of the Five Principles of S.O.L.I.D_Basic Knowledge
Foreword
What we are going to explain in this chapter is the fifth of the five principles of S.O.L.I.D JavaScript language implementation, the Dependency Inversion Principle LSP (The Dependency Inversion Principle).
English original text:http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
Dependency Inversion Principle
The description of the dependency inversion principle is:
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 depend on abstractions
B. Abstractions should not depend upon details. Details should depend upon abstractions.
Abstraction should not depend on details, details should depend on abstraction
The most important issue with the Dependency Inversion Principle is to ensure that the main components of an application or framework are decoupled from non-important low-level component implementation details. This will ensure that the most important parts of the program are not affected by changes in low-level components.
The first part of this principle is about the coupling method between high-level modules and low-level modules. In the traditional split architecture, high-level modules (encapsulating the core business logic of the program) always depend on some low-level modules (some basic points ). When applying the dependency inversion principle, 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 data for a program, the traditional design is that the core module depends on the API of a persistence module. After reconstruction according to the dependency inversion principle, the core module needs to define the persistence API interface, and then The persistence implementation instance needs to implement this API interface defined by the core module.
The second part of the principle describes the correct relationship between abstraction and detail. To understand this part, it is more helpful to understand the C language, because its applicability is more obvious.
Unlike some statically typed languages, C does not provide a language-level concept to define interfaces. What is the relationship between class definition and class implementation? In C, classes are defined in the form of header files. , which defines the class member methods and variables that the source file needs to implement. Because all variables and private methods are defined in header files, they can be used to abstract away implementation details. It is implemented by defining only abstract methods (abstract base class in C). The concept of interface is used to implement classes.
DIP and JavaScript
Because JavaScript is a dynamic language, there is no need to abstract for decoupling. So the change that abstractions should not depend on details does not have a big impact in JavaScript, but it does have a big impact that high-level modules should not depend on low-level modules.
When discussing the dependency inversion principle in the context of statically typed languages, the concept of coupling includes semantic and physical. That is to say, if a high-level module depends on a low-level module, it not only couples the semantic interface, but also couples the physical interface defined in the low-level module. In other words, high-level modules must not only be decoupled from third-party libraries, but also from native low-level modules.
To explain this, imagine a .NET program might contain a very useful high-level module that depends on a low-level persistence module. When the author needs to add a similar interface to the persistence API, regardless of whether the dependency inversion principle is used or not, the high-level module cannot be reused in other programs without reimplementing the new interface of the low-level module.
In JavaScript, the applicability of the dependency inversion principle is limited to the semantic coupling between high-level modules and low-level modules. For example, DIP can add interfaces as needed instead of coupling the implicit interfaces defined by low-level modules.
To understand this, let’s look at the following example:
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
});
在上述代码里,有个小型的JS类库将一个DIV转化成Map以便显示当前跟踪的位置信息。trackMap函数有2个依赖:第三方的Google Maps API和Location feed。该feed对象的职责是当icon位置更新的时候调用一个callback回调(在初始化的时候提供的)并且传入纬度latitude和精度longitude。Google Maps API是用来渲染界面的。
feed对象的接口可能按照装,也可能没有照装trackMap函数的要求去设计,事实上,他的角色很简单,着重在简单的不同实现,不需要和Google Maps这么依赖。介于trackMap语义上耦合了Google Maps API,如果需要切换不同的地图提供商的话那就不得不对trackMap函数进行重写以便可以适配不同的provider。
为了将于Google maps类库的语义耦合翻转过来,我们需要重写设计trackMap函数,以便对一个隐式接口(抽象出地图提供商provider的接口)进行语义耦合,我们还需要一个适配Google Maps API的一个实现对象,如下是重构后的trackMap函数:
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
});
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);
}
};
})();
When is dependency injection?
A bit irrelevant. In fact, the concept of dependency injection is often confused with the principle of dependency inversion. In order to clarify this difference, we need to explain:
Dependency injection is a special form of control inversion, which means how a component obtains its dependencies. Dependency injection means that dependencies are provided to components, rather than components obtaining dependencies. This means creating an instance of a dependency, requesting the dependency through the factory, and requesting the dependency through the Service Locator or the initialization of the component itself. The dependency inversion principle and dependency injection both focus on dependencies and are used for inversion. However, the dependency inversion principle does not focus on how components obtain 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 control inversion. What is reversed here is which module defines the interface (from low-level definition to high-level definition).
Summary
This is the last article of the five principles. In these five articles, we have seen how SOLID is implemented in JavaScript. Different principles are explained in JavaScript from different perspectives. (Uncle's note: In fact, I feel that although it is a bit nondescript, from another level, the general principles are actually the same in various languages.)