• 技术文章 >开发工具 >VSCode

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

    青灯夜游青灯夜游2023-02-20 19:39:58转载309

    本篇文章给大家浅析VSCode中依赖注入的原理,聊聊依赖注入做了什么?依赖注入怎么做?希望对大家有所帮助!

    团队推行 「依赖注入」有一段时间了,但每次使用时都觉得很陌生,有很多概念总是不知所云:服务id,服务描述符,服务装饰器等等。

    可能是因为不懂得其中原理,使用时都有种「虚」的感觉,最近通过阅读 VS Code 源码,拜读团队大佬的分享文章,力图理清其中的原理,在这里做一个简单的核心逻辑介绍。

    依赖注入做了什么

    假设以下情况:

    按照普通的写法就是:

    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 所依赖的模块:

    依赖注入怎么做?

    要实现这样一套功能,大致需要:

    下文会实现一个最简单的模型,涵盖主体流程。

    添加依赖信息

    如何给一个 类 打上烙印,声明它所依赖的服务呢?
    将问题再次抽象:如何给一个类加上额外的信息?
    其实,每个类在 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
    );

    模块创建

    具体思路为:

    这里先上一个简单的 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 整个依赖注入体系写的代码,进阶可以看这里。

    参考资料

    VS Code 源码 位置:src/vs/platform/instantiation/common
    本文借鉴了代码思路,且命名也高度一致(手动狗头

    更多关于VSCode的相关知识,请访问:vscode教程!!

    以上就是简单聊聊VSCode中依赖注入的原理的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    上一篇:VSCode支持go语言吗 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • 怎么配置同步?VSCode官方的配置同步方案分享• 手把手教你使用vscode连接远程服务器• 【整理分享】20个实用又好看VSCode主题 ,值得收藏!• 8 个VSCode中很棒的Laravel开发扩展• 【整理总结】VSCode常用插件和好用配置(小白必看)• 2023年最新最全的VScode插件推荐
    1/1

    PHP中文网