本篇文章給大家淺析vscode中依賴注入的原理,聊聊依賴注入做了什么?依賴注入怎么做?希望對大家有所幫助!
團隊推行 「依賴注入」有一段時間了,但每次使用時都覺得很陌生,有很多概念總是不知所云:服務id,服務描述符,服務裝飾器等等。
可能是因為不懂得其中原理,使用時都有種「虛」的感覺,最近通過閱讀 VS Code 源碼,拜讀團隊大佬的分享文章,力圖理清其中的原理,在這里做一個簡單的核心邏輯介紹。
依賴注入做了什么
假設以下情況:
-
服務模塊 A,依賴服務 B;
-
服務模塊 B;
-
功能模塊 Feature,依賴服務 A 和 B;
按照普通的寫法就是:
class?B?{} class?A?{ ????constructor()?{ ????????//?在?A?的構造器中?new?B ????????this.b?=?new?B(); ????} } class?Feature?{ ????constructor()?{ ????????this.a?=?new?A(); ????????this.b?=?new?B(); ????} } //?使用時 const?feature?=?new?Feature();
代碼簡單明了,存在一些問題,比如:如果 A 和 Feature 依賴的 B 需要是同一個實例,以上的寫法將會初始化兩個 B 實例。【推薦學習:vscode教程、vscode教程】
簡單修改一下:
class?A?{ ????constructor(b:?B)?{ ????????this.b?=?b; ????} } class?Feature?{ ????constructor(a,?b)?{ ????????this.a?=?a; ????????this.b?=?b; ????} } //?使用時 const?b?=?new?B(); const?a?=?new?A(b); const?feature?=?new?Feature(a,?b);
某個模塊初始化時,先在外部將其所依賴的模塊創建出來,通過參數的形式傳入功能模塊。這樣的寫法就是「依賴注入」。
現在這種寫法的問題在于:手動傳參的形式,必須人工保證 new 的順序,即必須獲得 a, b 實例才能執行 new Feature。
當依賴關系變得復雜時,創建一個功能模塊之前很有可能需要無數個基礎模塊,這時候復雜度將會非常高。類似于這種感覺:
想象一種模式:存在一個模塊控制器,或者說「服務管理器」來管理這些依賴關系:
class?Feature?{ ????//?聲明這個模塊依賴?idA,?idB ????idA ????idB } //?告知「服務管理器」,怎么找對應的模塊 services[idA]?=?A; services[idB]?=?B; //?使用時 const?feature?=?services.createInstance(Feature);
這個 services 承載的不就是之前的「手工」過程嗎?
在 createInstance(Feature) 時,分析 Feature 所依賴的模塊:
-
如果所依賴的模塊還未創建出實例,遞歸創建出該服務實例,最終返回;
-
如果所依賴的模塊已有實例,返回該實例;
-
找齊后通過參數注入 Feature,完成初始化;
vscode 實現的正是這么一套「依賴注入體系」。
依賴注入怎么做?
要實現這樣一套功能,大致需要:
-
一個類如何聲明其依賴的服務 id,即給定一個 類,外部如何知道他依賴了哪些服務?
-
如何管理管理服務?
-
如何創建某個模塊?
下文會實現一個最簡單的模型,涵蓋主體流程。
添加依賴信息
如何給一個 類 打上烙印,聲明它所依賴的服務呢?
將問題再次抽象:如何給一個類加上額外的信息?
其實,每個類在 es5 下都是 function,而每個 Function 說到底也只是 Object ,只要給 Object 加上幾個字段來標識所需要的服務 id,就可以完成所需要的功能。
通過 「參數裝飾器」的寫法,可以很容易做到這一點:
//?參數裝飾器? const?decorator?=?( ????target:?Object,?//?被裝飾的目標,這里為?Feature ????propertyName:?string,? ????index:?number?//?參數的位置索引 )?=>?{ ????target['deps']?=?[{????????index,????????id:?'idA',????}]; } class?Feature?{ ????name?=?'feature'; ????a:?any; ????constructor( ????????//?參數裝飾器 ????????@decorator?a:?any, ????)?{ ????????this.a?=?a; ????} } console.log('Feature.deps',?Feature['deps']); //?[{?id:?'idA',?index:?0?}]
通過這種方式,通過 Feature (之后會稱之為 構造器 ctor)就可以獲取到 serviceId。
服務管理
使用 map 來進行管理,一個 id 對應一個 服務 ctor。
class?A?{ ????name?=?'a'; } //?服務集 class?ServiceCollection?{ ????//?服務集合 ????//?key?為服務標識 ????//?value?為?服務ctor ????private?entries?=?new?Map<string>(); ????set(id:?string,?ctor:?any)?{ ????????this.entries.set(id,?ctor);??? ????} ????get(id:?string):?any?{ ????????return?this.entries.get(id); ????} } const?services?=?new?ServiceCollection(); //?聲明服務?A?id?為?idA services.set('idA',?A);</string>
示意圖如下:
現在,就可以通過 Feature 來找到所依賴的服務的構造器了
//?通過?Feature?找到所依賴的?A const?serviceId?=?Feature['deps'][0].id;?//?idA console.log( ????'Feature.deps',? ????services.get(serviceId)?//?A );
模塊創建
具體思路為:
-
如果所依賴的模塊還未創建出實例,遞歸創建出該服務實例,最終返回;
-
如果所依賴的模塊已有實例,返回該實例;
-
找齊后通過參數注入 Feature,完成初始化;
這里先上一個簡單的 demo,只有一層的依賴(即所依賴的服務沒有依賴其他服務),簡單的講,就是沒有遞歸能力:
class?InstantiationService?{ ????services:?ServiceCollection; ????constructor(services:?ServiceCollection)?{ ????????this.services?=?services; ????} ????createInstance(ctor:?any)?{ ????????//?1.?獲取?ctor?依賴的?服務id ????????//?結果為:?['idA'] ????????const?depIds?=?ctor['deps'].map((item:?any)?=>?item.id); ????????//?2.?獲取服務?id?對應的?服務構造器 ????????//?結果為:[A] ????????const?depCtors?=?depIds.map((id:?string)?=>?services.get(id)); ????????//?3.?獲取服務實例 ????????//?結果為:?[?A?{?name:?'a'}?] ????????const?args?=?depCtors.map((ctor:?any)?=>?new?ctor()); ????????//?4.?依賴的服務作為參數注入,實例化所需要模塊 ????????//?結果為:[?Feature?{?name:?'feature',?a?}] ????????const?result?=?new?ctor(...args); ????????return?result; ????} } const?instantiation?=?new?InstantiationService(services); //?使用時 const?feature?=?instantiation.createInstance(Feature);
至此,依賴注入的核心流程實現完畢,要使用 Feature 時,只需要調用 createInstance,不用管他所依賴的服務是否被初始化,instantiation 幫我們做了這個事情。
總結
本文簡單實現一個 demo 級別的「依賴注入」模型,簡單實現了:
-
模塊聲明所需要的依賴;
-
服務管理;
-
模塊創建;
以此為基礎,可以拓展出一些高級功能:
-
依賴收集:可用于分析每個模塊的依賴,且可以檢測是否存在「循環依賴」;
-
模塊銷毀:當模塊銷毀時,遞歸銷毀他所依賴的服務實例;
-
延遲初始化:創建依賴的服務時,選擇創建一個 proxy ,當真正使用時才真正創建實例;
-
異步依賴:當依賴的服務的創建過程是異步的情況下,如何執行創建邏輯;
vscode教程?本文代碼看這里。
vscode教程?參考 VSCode 整個依賴注入體系寫的代碼,進階可以看這里。
參考資料
VS Code 源碼 位置:src/vs/platform/instantiation/common
本文借鑒了代碼思路,且命名也高度一致(手動狗頭
更多關于VSCode的相關知識,請訪問:vscode教程!!