摘要:維基百科該原則規定高層次的模塊不應該依賴與低層次的模塊,兩者都應該依賴于抽象接口。依賴反轉原則則顛倒這種依賴關系,并以上面提到的兩個規定作為指導思想。維基百科這些話的意思就是將依賴對象的創建和綁定轉移到被依賴對象類的外部來實現。
在這個標題中,除了 JS 是亂入之外,其它的幾個詞匯都是存在一個共同點的,那就是依賴。
那么,依賴是什么呢?
比如,現在我正在寫這篇博客文,但是我得在電腦上編輯,電腦便是我完成這件事的依賴。而在代碼中,最直觀的體現是模塊之間的依賴。如某個模塊依賴另外一個模塊,那么另外的那個模塊就是該模塊的依賴。其實在上篇博客文章《JaVaScript中的模塊》中,我們也手寫了一個模塊依賴管理器。
依賴這個理解起來很簡單,但這不代表可以隨意的依賴。在寫模塊的時候,講究個高內聚低耦合,以提高模塊的可拓展性和可維護性。模塊依賴了誰,怎么去依賴,都關乎了最終模塊的好與壞。
還好在編程界有著提高代碼質量的金科玉律,我們可以用理論來指導實踐,寫出更好的代碼。
依賴反轉原則依賴反轉原則(Dependency inversion principle,DIP),是一種特定的解耦形式,使得高層次的模塊不依賴于低層次的模塊的實現細節,依賴關系被顛倒(反轉),從而使得低層次模塊依賴于高層次模塊的需求抽象。———— 維基百科
該原則規定:
高層次的模塊不應該依賴與低層次的模塊,兩者都應該依賴于抽象接口。
抽象接口不應該依賴于具體實現。而具體實現則應該依賴于抽象接口。
現在用一個例子來解釋一波。
// Ajax.js class Ajax { get() { return this.constructor.name; } } export default Ajax; // main.js import Ajax from "./Ajax"; class Main { constructor() { this.render() } render() { let content = (new Ajax()).get(); console.log("content from", content); } } new Main();
剛開始的時候,我們基于 XMLHttpRequest 對象,封裝了 Ajax 用于請求數據。后來 fetch 出來了,我們打算跟上時代的腳步,封裝 fetch 以取代 Ajax。
// Fetch.js class Fetch { fetch() { return this.constructor.name; } } export default Fetch; // main.js import Fetch from "./Fetch"; class Main { constructor() { this.render(); } render() { let content = (new Fetch()).fetch(); console.log("content from", content); } } new Main();
從以上可以看出來,整個替代過程很麻煩,我們需要找出封裝請求模塊(Ajax、Fetch)的所有引用,然后替換掉。又由于 Ajax、Fetch 的方法命名也是不同,所以也需要對應地做更改。
這就是傳統的處理依賴關系的方式。在這里 Main 是高層次模塊,Ajax、Fetch 是低層次模塊。依賴關系創建于高層次模塊,且高層次模塊直接依賴低層次模塊,這種依賴關系限制了高層次模塊的復用性。
依賴反轉原則則顛倒這種依賴關系,并以上面提到的兩個規定作為指導思想。
// Service.js class Service { request(){ throw `${this.constructor.name} 沒有實現 request 方法!` } } class Ajax extends Service { request(){ return this.constructor.name; } } export default Ajax; // Main.js import Service from "./Service.js"; class Main { constructor() { this.render(); } render() { let content = (new Service).request(); console.log("content from", content); } } new Main();
在這里我們把共同依賴的 Service 作為抽象接口,它就是高層次模塊與低層次模塊需要共同遵守的契約。在高層次模塊中,它會默認 Service 會有 request 方法用來請求數據。在低層次模塊中,它會遵從 Service 復寫應該存在的方法。這在《在JavaScript中嘗試組合模式》中,無論分支對象還是葉對象都實現 expense() 方法的道理差不多。
即使后來需要封裝 axios 取代 fetch,我們也只需要在 Service.js 中修改即可。
再次回顧下傳統的依賴關系。
依賴關系創建于高層次模塊,且高層次模塊直接依賴低層次模塊。
經過以上的折騰,我們充其量只是解決了高層次模塊直接依賴低層次模塊的問題。那么依賴關系創建于高層次模塊的問題呢?
控制反轉如果說依賴反轉原則告訴我們該依賴誰,那么控制反轉則告訴們誰應該來控制依賴。
像上面的 Main 模塊,它依賴 Service 模塊。為了獲得 Service 實例的引用,Main 在內部靠自身 new 出了一個 Service 實例。這樣明顯地引用其它模塊,無異加大了模塊間的耦合。
控制反轉(Inversion of Control,IoC),通過控制反轉,對象在被創建的時候,有一個控制系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。可以說,依賴被注入到對象中。———— 維基百科
這些話的意思就是將依賴對象的創建和綁定轉移到被依賴對象類的外部來實現。實現控制反轉最常見的方式是依賴注入,還有一種方式依賴查找。
依賴注入依賴注入(Dependency Injection,DI),在軟件工程中,依賴注入是種實現控制反轉用于解決依賴性設計模式。一個依賴關系指的是可被利用的一種對象(即服務提供端)。依賴注入是將所依賴的傳遞給將使用的從屬對象(即客戶端)。該服務將會變成客戶端的狀態的一部分。傳遞服務給客戶端,而非允許客戶端來建立或尋找服務,是本設計模式的基本要求。
沒看懂?沒關系。這句話講的是,把過程放在外面,將結果帶入內部。在《JaVaScript中的模塊》中,我們已經用到過依賴注入,就是對于依賴模塊的模塊,則把依賴作為參數使用。
所以我們再次改造下,
// Service.js class Service { request() { throw `${this.constructor.name} 沒有實現 request 方法!` } } class Ajax extends Service { request() { return this.constructor.name; } } export default Ajax; // Main.js class Main { constructor(options) { this.Service = options.Service; this.render(); } render() { let content = this.Service.request(); console.log("content from", content); } } export default Main; // index.js import Service from "./Service.js"; import Main from "./Main.js"; new Main({ Service: new Service() })
在 Main 模塊中, Service 的實例化是在外部完成,并在 index.js 中注入。相比上一次,改動后的代碼并沒有看出帶來多大的好處。如果我們再增加一個模塊呢?
class Router { constructor() { this.init(); } init() { console.log("Router::init") } } export default Router;
# Main.js + this.Service = options.Router; # index.js + import Router from "./Router.js" new Main({ + Router: new Service() })
若是內部實例化就不好處理了。可換成依賴注入后,這個問題就很好解決了。
// utils.js export const toOptions = params => Object.entries(params).reduce((accumulator, currentValue) => { accumulator[currentValue[0]] = new currentValue[1]() return accumulator; }, {}); // Main.js class Main { constructor(options) { Object.assign(this, options); this.render(); } render() { let content = this.Service.request(); console.log("content from", content); } } export default Main; // index.js import Service from "./Service.js"; import Router from "./Router.js"; import Main from "./Main.js"; import { toOptions } from "./utils.js" /** * toOptions 轉換成參數形式 * @params {Object} 類 * @return {Object} {Service: Service實例, Router: Router實例} */ const options = toOptions({Service, Router}); new Main(options);
因為依賴注入把依賴的引用從外部引入,所以這里使用 Object.assign(this, options) 方式,把依賴全部加到了 this 上。即使再增加模塊,也只需要在 index.js 中引入即可。
到了這里,DIP、IoC、DI 的概念應該有個清晰的認識了。然后我們再結合實際,加個功能再次鞏固以下。作為一功能個獨立的模塊,一般都有個初始化的過程。
現在我們要做的是遵守一個初始化的約定,定義一個抽象接口,
// Interface.js export class Service { request() { throw `${this.constructor.name} 沒有實現 request 方法!` } } export class Init { init() { throw `${this.constructor.name} 沒有實現 init 方法!` } } // Service.js import { Init, Service } from "./Interface.js"; import { mix } from "./utils.js" class Ajax extends mix(Init, Service) { constructor() { super(); } init() { console.log("Service::init") } request() { return this.constructor.name; } } export default Ajax;
Main、Service、Router 都依賴 Init 接口(在這里就是一種協定),Service 模塊比較特殊,所以做了 Mixin 處理。要做到統一初始化,Main 還需要做些事。
// Main.js import { Init } from "./Interface.js" class Main extends Init { constructor(options) { super(); Object.assign(this, options); this.options = options; this.render(); } init() { (Object.values(this.options)).map(item => item.init()); console.log("Main::init"); } render() { let content = this.Service.request(); console.log("content from", content); } } export default Main;
至此,結束
// index.js import Service from "./Service.js"; import Router from "./Router.js"; import Main from "./Main.js"; import { toOptions } from "./utils.js" /** * toOptions * 轉換成參數形式 * @params {Object} 類 * @return {Object} * { * Service: Service實例, * Router: Router實例 * } */ const options = toOptions({ Service, Router }); (new Main(options)).init(); // content from Ajax // Service::init // Router::init // Main::init
(以上所有示例可見GitHub)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109275.html
摘要:服務本省作為一個高層類,對外提供訪問,卻受制于提供具體服務的服務提供者定義的實現,高層模塊依賴底層模塊實現,違背了依賴倒置原則。遵循依賴倒置原則的例子場景同介紹中場景。 1. 名詞介紹 OOD,面向對象設計 DIP,依賴倒置(軟件設計原則) IOC,控制反轉(軟件設計模式) DI,依賴注入 IOC Container,控制反轉容器,也是依賴注入容器 2. 組成部分 服務清單(功能...
摘要:在中使用解耦,有兩種注入方式構造函數注入屬性注入。對象的實例化解析依賴信息該方法實質上就是通過的反射機制,通過類的構造函數的參數分析他所依賴的單元。 有關概念 依賴倒置原則(Dependence Inversion Principle, DIP) 傳統軟件設計中,上層代碼依賴于下層代碼,當下層出現變動時,上層也要相應變化。 DIP的核心思想是:上層定義接口,下層實現這個接口,從而使的下...
摘要:構造器注入實現特定參數的構造函數,在新建對象時傳入所依賴類型的對象。 基本概念 1.依賴倒置(反轉)原則(DIP):一種軟件架構設計的原則(抽象概念,是一種思想)在面向對象編程領域中,依賴反轉原則(Dependency inversion principle,DIP)是指一種特定的解耦(傳統的依賴關系創建在高層次上,而具體的策略設置則應用在低層次的模塊上)形式,使得高層次的模塊不依賴于...
摘要:依賴注入控制反轉的一種具體實現方法。接下來,我們使用依賴注入實現控制反轉,使依賴關系倒置依賴被動傳入。從單元測試的角度看,依賴注入更方便和操作,方便了測試人員寫出質量更高的測試代碼。 前言 好的設計會提高程序的可復用性和可維護性,也間接的提高了開發人員的生產力。今天,我們就來說一下在很多框架中都使用的依賴注入。 一些概念 要搞清楚什么是依賴注入如何依賴注入,首先我們要明確一些概念。 D...
摘要:可以為服務提供者的方法設置類型提示。方法將在所有其他服務提供者均已注冊之后調用。所有服務提供者都在配置文件中注冊。可以選擇推遲服務提供者的注冊,直到真正需要注冊綁定時,這樣可以提供應用程序的性能。 本文最早發布于 Rootrl的Blog 導言 Laravel是一款先進的現代化框架,里面有一些概念非常重要。在上手Laravel之前,我認為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP...
閱讀 1841·2021-08-19 11:12
閱讀 1418·2021-07-25 21:37
閱讀 979·2019-08-30 14:07
閱讀 1259·2019-08-30 13:12
閱讀 644·2019-08-30 11:00
閱讀 3522·2019-08-29 16:28
閱讀 982·2019-08-29 15:33
閱讀 2960·2019-08-26 13:40