本篇文章給大家淺析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教學、程式設計教學】
#簡單修改一下:
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, any>(); 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);
示意圖如下:
現在,就可以透過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 層級的「依賴注入」模型,簡單實作了:
模組宣告所需要的依賴;
服務管理;
模組建立;
本文程式碼看這裡。 完整功能
參考 VSCode 整個依賴注入系統所寫的程式碼,進階可以看這裡。 參考資料
更多關於VSCode的相關知識,請造訪:
以上是簡單聊聊VSCode中依賴注入的原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!