深刻理解JavaScript系列(22):S.O.L.I.D五大原則之依賴倒置原則DIP

前言

本章咱們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第5篇,依賴倒置原則LSP(The Dependency Inversion Principle )。javascript

英文原文:http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/

依賴倒置原則

依賴倒置原則的描述是:java

A. 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.
抽象不該該依賴於細節,細節應該依賴於抽象

依賴倒置原則的最重要問題就是確保應用程序或框架的主要組件從非重要的底層組件實現細節解耦出來,這將確保程序的最重要的部分不會由於低層次組件的變化修改而受影響。git

該原則的第一部分是關於高層模塊和低層模塊之間的耦合方式,在傳統的分紅架構中,高層模塊(封裝了程序的核心業務邏輯)總依賴於低層的一些模塊(一些基礎點)。當應用依賴倒置原則的時候,關係就反過來了。和高層模塊依賴於低層模塊不一樣,依賴倒置是讓低層模塊依賴於高層模塊裏定義的接口。舉例來講,若是要給程序進行數據持久化,傳統的設計是核心模塊依賴於一個持久化模塊的API,而根據依賴倒置原則重構之後,則是核心模塊須要定義持久化的API接口,而後持久化的實現實例須要實現核心模塊定義的這個API接口。canvas

該原則的第二部分描述的是抽象和細節之間的正確關係。理解這一部分,經過了解C++語言比較有幫助,由於他的適用性比較明顯。架構

不像一些靜態類型的語言,C++沒有提供一個語言級別的概念來定義接口,那類定義和類實現之間究竟是怎麼樣的呢,在C++裏,類經過頭文件的形式來定義,其中定義了源文件須要實現的類成員方法和變量。由於全部的變量和私有方法都定義在頭文件裏,因此能夠用來抽象以便和實現細節以前解耦出來。經過定只定義抽象方法來實現(C++裏是抽象基類)接口這個概念用於實現類來實現。框架

DIP and JavaScript

由於JavaScript是動態語言,因此不須要去爲了解耦而抽象。因此抽象不該依賴於細節這個改變在JavaScript裏沒有太大的影響,但高層模塊不該依賴於低層模塊卻有很大的影響。ide

在當靜態類型語言的上下文裏討論依賴倒置原則的時候,耦合的概念包括語義(semantic)和物理(physical)兩種。這就是說,若是一個高層模塊依賴於一個低層模塊,也就是不只耦合了語義接口,也耦合了在底層模塊裏定義的物理接口。也就是說高層模塊不只要從第三方類庫解耦出來,也須要從原生的低層模塊裏解耦出來。函數

爲了解釋這一點,想象一個.NET程序可能包含一個很是有用的高層模塊,而該模塊依賴於一個低層的持久化模塊。看成者須要在持久化API裏增長一個相似的接口的時候,無論依賴倒置原則有沒有使用,高層模塊在不從新實現這個低層模塊的新接口以前是沒有辦法在其它的程序裏獲得重用的。this

在JavaScript裏,依賴倒置原則的適用性僅僅限於高層模塊和低層模塊之間的語義耦合,好比,DIP能夠根據須要去增長接口而不是耦合低層模塊定義的隱式接口。google

爲了來理解這個,咱們看一下以下例子:

$.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
});

在上述代碼裏,有個小型的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函數:

$.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
});

在該版本里,咱們從新設計了trackMap函數以及須要的一個地圖提供商接口,而後將實現的細節挪到了一個單獨的googleMapsProvider組件,該組件可能獨立封裝成一個單獨的JavaScript模塊。以下是個人googleMapsProvider實現:

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);
}
};
})();

作了上述這些改變之後,trackMap函數將變得很是有彈性了,沒必要依賴於Google Maps API,相反能夠任意替換其它的地圖提供商,那就是說能夠按照程序的需求去適配任何地圖提供商。

什麼時候依賴注入?

有點不太相關,其實依賴注入的概念常常和依賴倒置原則混在一塊兒,爲了澄清這個不一樣,咱們有必要來解釋一下:

依賴注入是控制反轉的一個特殊形式,反轉的意思一個組件如何獲取它的依賴。依賴注入的意思就是:依賴提供給組件,而不是組件去獲取依賴,意思是建立一個依賴的實例,經過工廠去請求這個依賴,經過Service Locator或組件自身的初始化去請求這個依賴。依賴倒置原則和依賴注入都是關注依賴,而且都是用於反轉。不過,依賴倒置原則沒有關注組件如何獲取依賴,而是隻關注高層模塊如何從低層模塊裏解耦出來。某種意義上說,依賴倒置原則是控制反轉的另一種形式,這裏反轉的是哪一個模塊定義接口(從低層裏定義,反轉到高層裏定義)。

總結

這是五大原則的最後一篇了,在這5篇文字裏咱們看到了SOLID如何在JavaScript裏實現的,不一樣的原則在JavaScript裏經過不一樣的角度來講明的。(大叔注:其實大叔以爲雖然是有點不三不四,但從另一個層面上說,大致的原則在各類語言上其實仍是同樣的。)

相關文章
相關標籤/搜索