首頁 >開發工具 >VSCode >簡單聊聊VSCode中依賴注入的原理

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

青灯夜游
青灯夜游轉載
2023-02-07 18:18:482108瀏覽

本篇文章給大家淺析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教學程式設計教學

#簡單修改一下:

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, 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(&#39;idA&#39;, A);

示意圖如下:

現在,就可以透過Feature 來找到所依賴的服務的建構器了

// 通过 Feature 找到所依赖的 A
const serviceId = Feature[&#39;deps&#39;][0].id; // idA
console.log(
    &#39;Feature.deps&#39;, 
    services.get(serviceId) // A
);

#模組創建

具體思路為:

  • 如果所依賴的模組尚未建立出實例,則遞歸創建出該服務實例,最終返回;

  • 如果所依賴的模組已有實例,返回該實例;

  • #找齊後透過參數注入Feature,完成初始化;

這裡先上一個簡單的demo,只有一層的依賴(即所依賴的服務沒有依賴其他服務),簡單的講,就是沒有遞歸能力:

class InstantiationService {
    services: ServiceCollection;

    constructor(services: ServiceCollection) {
        this.services = services;
    }

    createInstance(ctor: any) {
        // 1. 获取 ctor 依赖的 服务id
        // 结果为: [&#39;idA&#39;]
        const depIds = ctor[&#39;deps&#39;].map((item: any) => item.id);

        // 2. 获取服务 id 对应的 服务构造器
        // 结果为:[A]
        const depCtors = depIds.map((id: string) => services.get(id));

        // 3. 获取服务实例
        // 结果为: [ A { name: &#39;a&#39;} ]
        const args = depCtors.map((ctor: any) => new ctor());

        // 4. 依赖的服务作为参数注入,实例化所需要模块
        // 结果为:[ Feature { name: &#39;feature&#39;, 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 整個依賴注入系統所寫的程式碼,進階可以看這裡。 參考資料

VS Code 原始碼位置:src/vs/platform/instantiation/common

本文借鑒了程式碼思路,且命名也高度一致(手動狗頭


更多關於VSCode的相關知識,請造訪:

vscode教學

!!#

以上是簡單聊聊VSCode中依賴注入的原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除