簡單聊聊VSCode中依賴注入的原理

本篇文章給大家淺析vscode中依賴注入的原理,聊聊依賴注入做了什么?依賴注入怎么做?希望對大家有所幫助!

簡單聊聊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。

當依賴關系變得復雜時,創建一個功能模塊之前很有可能需要無數個基礎模塊,這時候復雜度將會非常高。類似于這種感覺:

簡單聊聊VSCode中依賴注入的原理

想象一種模式:存在一個模塊控制器,或者說「服務管理器」來管理這些依賴關系:

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>

示意圖如下:

簡單聊聊VSCode中依賴注入的原理

現在,就可以通過 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)?=&gt;?item.id);  ????????//?2.?獲取服務?id?對應的?服務構造器 ????????//?結果為:[A] ????????const?depCtors?=?depIds.map((id:?string)?=&gt;?services.get(id));  ????????//?3.?獲取服務實例 ????????//?結果為:?[?A?{?name:?'a'}?] ????????const?args?=?depCtors.map((ctor:?any)?=&gt;?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 級別的「依賴注入」模型,簡單實現了:

  • 模塊聲明所需要的依賴;

  • 服務管理;

  • 模塊創建;

以此為基礎,可以拓展出一些高級功能:

  • 模塊創建(遞歸):VSCode 用了 + 圖 做了這件事,算法也不復雜;

  • 依賴收集:可用于分析每個模塊的依賴,且可以檢測是否存在「循環依賴」;

  • 模塊銷毀:當模塊銷毀時,遞歸銷毀他所依賴的服務實例;

  • 延遲初始化:創建依賴的服務時,選擇創建一個 proxy ,當真正使用時才真正創建實例;

  • 異步依賴:當依賴的服務的創建過程是異步的情況下,如何執行創建邏輯;

vscode教程?本文代碼看這里。
vscode教程?參考 VSCode 整個依賴注入體系寫的代碼,進階可以看這里。

參考資料

VS Code 源碼 位置:src/vs/platform/instantiation/common
本文借鑒了代碼思路,且命名也高度一致(手動狗頭

更多關于VSCode的相關知識,請訪問:vscode教程!!

? 版權聲明
THE END
喜歡就支持一下吧
點贊5 分享