搜索
首页开发工具VSCode简单聊聊VSCode中依赖注入的原理

简单聊聊VSCode中依赖注入的原理

Feb 07, 2023 pm 06:18 PM
vscode前端visual studio code

本篇文章给大家浅析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。

当依赖关系变得复杂时,创建一个功能模块之前很有可能需要无数个基础模块,这时候复杂度将会非常高。类似于这种感觉:

杂乱的依赖关系

想象一种模式:存在一个模块控制器,或者说「服务管理器」来管理这些依赖关系:

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中文网其他相关文章!

声明
本文转载于:掘金社区。如有侵权,请联系admin@php.cn删除
Visual Studio和VS代码:了解其关键差异Visual Studio和VS代码:了解其关键差异Apr 19, 2025 am 12:16 AM

VisualStudio适合大型项目和企业级应用开发,VSCode则适合快速开发和多语言支持。1.VisualStudio提供全面的IDE环境,支持微软技术栈。2.VSCode是轻量级编辑器,强调灵活性和扩展性,跨平台支持。

视觉工作室仍然免费吗?了解可用性视觉工作室仍然免费吗?了解可用性Apr 18, 2025 am 12:05 AM

是的,VisualStudio某些版本是免费的。具体来说,VisualStudioCommunityEdition对个人开发者、开源项目、学术研究和小型组织免费。然而,也有付费版本如VisualStudioProfessional和Enterprise,适用于大型团队和企业,提供额外功能。

使用Visual Studio:跨平台开发软件使用Visual Studio:跨平台开发软件Apr 17, 2025 am 12:13 AM

使用VisualStudio进行跨平台开发是可行的,通过支持.NETCore和Xamarin等框架,开发者可以编写一次代码并在多个操作系统上运行。1)创建.NETCore项目并使用其跨平台能力,2)使用Xamarin进行移动应用开发,3)利用异步编程和代码重用来优化性能,确保应用的高效运行和可维护性。

vscode如何格式化jsonvscode如何格式化jsonApr 16, 2025 am 07:54 AM

在 VS Code 中格式化 JSON 的方法有:1. 使用快捷键 (Windows/Linux:Ctrl Shift I;macOS:Cmd Shift I);2. 通过菜单(“编辑” > “格式化文档”);3. 安装 JSON 格式化程序扩展(如 Prettier);4. 手动格式化(使用快捷键缩进/缩出块或添加花括号和分号);5. 使用外部工具(如 JSONLint 和 JSON Formatter)。

vscode如何编译vscode如何编译Apr 16, 2025 am 07:51 AM

在 VSCode 中编译代码分 5 步:安装 C 扩展;在项目文件夹中创建 "main.cpp" 文件;配置编译器(如 MinGW);使用快捷键("Ctrl Shift B")或 "Build" 按钮编译代码;使用快捷键("F5")或 "Run" 按钮运行编译后的程序。

vscode如何安装vscode如何安装Apr 16, 2025 am 07:48 AM

要安装 Visual Studio Code,请按以下步骤操作:访问官方网站 https://code.visualstudio.com/;根据操作系统下载安装程序;运行安装程序;接受许可协议并选择安装路径;安装完成后,VSCode 将自动启动。

vscode如何放大字体vscode如何放大字体Apr 16, 2025 am 07:45 AM

在 Visual Studio Code 中放大字体的方法有:打开设置面板(Ctrl , 或 Cmd ,)。搜索并调整“Font Size”。选择具有适合大小的“Font Family”。安装或选择提供合适大小的主题。使用键盘快捷键(Ctrl 或 Cmd )放大字体。

vscode如何连接远程服务器vscode如何连接远程服务器Apr 16, 2025 am 07:42 AM

如何通过 VSCode 连接远程服务器?安装 Remote - SSH 扩展配置 SSH在 VSCode 中创建连接输入连接信息:主机、用户名、端口、SSH 密钥在 Remote Explorer 中双击已保存的连接

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具