摘要:前言本章我們要講解的是五大原則語言實現的第篇,依賴倒置原則。當應用依賴倒置原則的時候,關系就反過來了。在當靜態類型語言的上下文里討論依賴倒置原則的時候,耦合的概念包括語義和物理兩種。依賴倒置原則和依賴注入都是關注依賴,并且都是用于反轉。
前言
本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第5篇,依賴倒置原則LSP(The Dependency Inversion Principle )。
英文原文:http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/
依賴倒置原則的描述是:
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.
抽象不應該依賴于細節,細節應該依賴于抽象
依賴倒置原則的最重要問題就是確保應用程序或框架的主要組件從非重要的底層組件實現細節解耦出來,這將確保程序的最重要的部分不會因為低層次組件的變化修改而受影響。
該原則的第一部分是關于高層模塊和低層模塊之間的耦合方式,在傳統的分成架構中,高層模塊(封裝了程序的核心業務邏輯)總依賴于低層的一些模塊(一些基礎點)。當應用依賴倒置原則的時候,關系就反過來了。和高層模塊依賴于低層模塊不同,依賴倒置是讓低層模塊依賴于高層模塊里定義的接口。舉例來說,如果要給程序進行數據持久化,傳統的設計是核心模塊依賴于一個持久化模塊的API,而根據依賴倒置原則重構以后,則是核心模塊需要定義持久化的API接口,然后持久化的實現實例需要實現核心模塊定義的這個API接口。
該原則的第二部分描述的是抽象和細節之間的正確關系。理解這一部分,通過了解C++語言比較有幫助,因為他的適用性比較明顯。
不像一些靜態類型的語言,C++沒有提供一個語言級別的概念來定義接口,那類定義和類實現之間到底是怎么樣的呢,在C++里,類通過頭文件的形式來定義,其中定義了源文件需要實現的類成員方法和變量。因為所有的變量和私有方法都定義在頭文件里,所以可以用來抽象以便和實現細節之前解耦出來。通過定只定義抽象方法來實現(C++里是抽象基類)接口這個概念用于實現類來實現。
DIP and JavaScript因為JavaScript是動態語言,所以不需要去為了解耦而抽象。所以抽象不應依賴于細節這個改變在JavaScript里沒有太大的影響,但高層模塊不應依賴于低層模塊卻有很大的影響。
在當靜態類型語言的上下文里討論依賴倒置原則的時候,耦合的概念包括語義(semantic)和物理(physical)兩種。這就是說,如果一個高層模塊依賴于一個低層模塊,也就是不僅耦合了語義接口,也耦合了在底層模塊里定義的物理接口。也就是說高層模塊不僅要從第三方類庫解耦出來,也需要從原生的低層模塊里解耦出來。
為了解釋這一點,想象一個.NET程序可能包含一個非常有用的高層模塊,而該模塊依賴于一個低層的持久化模塊。當作者需要在持久化API里增加一個類似的接口的時候,不管依賴倒置原則有沒有使用,高層模塊在不重新實現這個低層模塊的新接口之前是沒有辦法在其它的程序里得到重用的。
在JavaScript里,依賴倒置原則的適用性僅僅限于高層模塊和低層模塊之間的語義耦合,比如,DIP可以根據需要去增加接口而不是耦合低層模塊定義的隱式接口。
為了來理解這個,我們看一下如下例子:
$.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里通過不同的角度來說明的。(大叔注:其實大叔覺得雖然是有點不倫不類,但從另外一個層面上說,大體的原則在各種語言上其實還是一樣的。)
關于本文本文轉自TOM大叔的深入理解JavaScript系列。關于S.O.L.I.D系列的五篇文章我糾結了很久,本來不想去整理的,但最終發現其實中間說的很多都是關于OOP(面向對象)編碼原則的東西,十分值得研讀,所以最后還是決定整理出來。
整理完這五篇我真的是醉了,尤其最后的三篇,越看越看不懂,不知道是不是因為原文比較晦澀難懂,導致大叔本人翻譯的也比較拗口,反正最終的結果就是后面的三篇,我也只是看了個一知半解。有什么問題,歡迎討論交流。
【深入理解JavaScript系列】文章,包括了原創,翻譯,轉載,整理等各類型文章,原文是TOM大叔的一個非常不錯的專題,現將其重新整理發布。謝謝大叔。如果你覺得本文不錯,請幫忙點個推薦,支持一把,感激不盡。
更多優秀文章歡迎關注我的專欄
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78478.html
摘要:前言本章我們要講解的是五大原則語言實現的第篇,接口隔離原則。接口隔離原則和單一職責有點類似,都是用于聚集功能職責的,實際上可以被理解才具有單一職責的程序轉化到一個具有公共接口的對象。與我們下面討論的一些小節是里關于違反接口隔離原則的影響。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第4篇,接口隔離原則ISP(The Interface Segreg...
摘要:,開始我們的第一篇單一職責。通過解耦可以讓每個職責工更加有彈性地變化。關于本文本文轉自大叔的深入理解系列。深入理解系列文章,包括了原創,翻譯,轉載,整理等各類型文章,原文是大叔的一個非常不錯的專題,現將其重新整理發布。 前言 Bob大叔提出并發揚了S.O.L.I.D五大原則,用來更好地進行面向對象編程,五大原則分別是: The Single Responsibility Princi...
摘要:前言本章我們要講解的是五大原則語言實現的第篇,里氏替換原則。因此,違反了里氏替換原則。與行為有關,而不是繼承到現在,我們討論了和繼承上下文在內的里氏替換原則,指示出的面向對象。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第3篇,里氏替換原則LSP(The Liskov Substitution Principle )。英文原文:http://fre...
摘要:前言本章我們要講解的是五大原則語言實現的第篇,開閉原則。該代碼有一個限制,就是如果再增加一個類型的話,那就需要再次修改里的條件語句,這明顯違反了開閉原則。關于本文本文轉自大叔的深入理解系列。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第2篇,開閉原則OCP(The Open/Closed Principle )。 開閉原則的描述是: Softwar...
摘要:可以為服務提供者的方法設置類型提示。方法將在所有其他服務提供者均已注冊之后調用。所有服務提供者都在配置文件中注冊。可以選擇推遲服務提供者的注冊,直到真正需要注冊綁定時,這樣可以提供應用程序的性能。 本文最早發布于 Rootrl的Blog 導言 Laravel是一款先進的現代化框架,里面有一些概念非常重要。在上手Laravel之前,我認為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP...
閱讀 2847·2021-09-10 10:51
閱讀 2215·2021-09-02 15:21
閱讀 3206·2019-08-30 15:44
閱讀 869·2019-08-29 18:34
閱讀 1652·2019-08-29 13:15
閱讀 3322·2019-08-26 11:37
閱讀 2697·2019-08-26 10:46
閱讀 1107·2019-08-26 10:26