VSCode用它管理上千个服务:依赖注入从入门到实战

VSCode用它管理上千个服务:依赖注入从入门到实战

摘要 :你的代码里到处都是 new?类之间紧密耦合难以测试?本文从最基础的依赖注入概念讲起,逐步深入到装饰器与DI容器,最后剖析VSCode如何用依赖注入系统优雅地管理整个应用架构。读完你将理解为什么依赖注入是现代应用架构的基石。

引言

在日常开发中,你可能经常写出这样的代码:

typescript 复制代码
class UserService {
    private userRepo = new UserRepo();
    private logger = new Logger();
    
    public findUser(id: string) {
        this.logger.log('查询用户');
        return this.userRepo.findById(id);
    }
}

这段代码看起来很直观,但隐藏着一个严重的问题:UserService 和它的依赖 UserRepoLogger 紧紧地绑在了一起。想要替换数据库实现?想要在测试中 Mock Logger?几乎不可能。

更糟糕的是,当你的应用规模扩大,类之间的依赖关系变成一张复杂的网,你会发现:

  • 修改一个类的构造函数,可能需要修改几十个地方
  • 单元测试变得异常困难,因为无法隔离依赖
  • 想要支持不同环境的配置,需要到处修改 new 语句

有没有更优雅的方式?有的,这就是依赖注入(Dependency Injection,DI)

VSCode 作为一个拥有数百万行代码的大型项目,正是通过依赖注入系统来管理成百上千个服务。今天,我们就从最基础的概念开始,一步步理解依赖注入的精髓,最后看看 VSCode 是如何实现工业级的 DI 系统的。

什么是依赖注入

依赖注入(Dependency Injection,DI) 是一种设计模式,核心思想非常简单:

不要在类内部创建依赖对象,而是从外部传入所需的依赖。

这个简单的改变,带来了四个重要的好处:

  • 降低耦合度:类不需要知道具体的实现,只依赖于抽象接口
  • 提高可测试性:可以轻松注入 Mock 对象进行单元测试
  • 增强灵活性:可以在运行时动态切换不同的实现
  • 符合 SOLID 原则:特别是依赖倒置原则(Dependency Inversion Principle)

让我们用一个简单的例子来理解这个转变:

传统方式(紧耦合)

typescript 复制代码
class UserService {
    // ❌ 在内部创建依赖
    private userRepo = new UserRepo();
    
    public findUser(id: string) {
        return this.userRepo.findById(id);
    }
}

在这个例子中,UserRepo 是在 UserService 内部实例化的,两者紧密耦合。如果想把 UserRepo 替换成其他实现(比如换一个数据库),你必须修改 UserService 的代码。

依赖注入方式(松耦合)

typescript 复制代码
class UserService {
    private userRepo: UserRepo; // 只声明依赖类型
    
    // ✅ 从外部传入依赖
    constructor(userRepo: UserRepo) {
        this.userRepo = userRepo;
    }
    
    public findUser(id: string) {
        return this.userRepo.findById(id);
    }
}

// 使用时在外部创建依赖并注入
const userRepo = new UserRepo();
const userService = new UserService(userRepo);

现在,UserRepoUserService 外部实例化,并通过构造函数传入。UserService 只需要知道依赖的接口,不需要关心具体实现。这样就可以轻松替换成其他实现,甚至在测试中使用 Mock 对象:

typescript 复制代码
// 生产环境使用真实实现
const userService = new UserService(new UserRepo());

// 测试环境使用 Mock
const userService = new UserService(new MockUserRepo());

这就是依赖注入的本质:控制权的反转(Inversion of Control,IoC)。依赖对象的创建权从类内部转移到了外部,类本身只负责使用这些依赖。

手动依赖注入:最基础的实践

理解了概念之后,让我们通过一个更完整的例子来看看依赖注入的实际应用。

第一步:定义接口,而非具体实现

这是依赖注入的关键原则:依赖于抽象,而非具体实现。

typescript 复制代码
// 定义日志接口
interface Logger {
    log(message: string): void;
}

// 定义数据库接口
interface DatabaseService {
    save(data: any): Promise<void>;
    findById(id: string): Promise<any>;
}

注意,这里我们只定义了接口,没有涉及任何具体实现。这样做的好处是:UserService 将只依赖这些接口,不依赖任何具体的实现类。

第二步:为不同场景提供不同实现

接口定义好之后,我们可以为不同的场景提供不同的实现:

typescript 复制代码
// 控制台日志实现
class ConsoleLogger implements Logger {
    log(message: string): void {
        console.log(`[LOG] ${new Date().toISOString()}: ${message}`);
    }
}

// 文件日志实现
class FileLogger implements Logger {
    log(message: string): void {
        // 实际项目中这里会写入文件
        console.log(`[FILE] ${message}`);
    }
}

// MongoDB 数据库实现
class MongoDBService implements DatabaseService {
    async save(data: any): Promise<void> {
        console.log('保存到 MongoDB:', data);
    }
    
    async findById(id: string): Promise<any> {
        console.log('从 MongoDB 查询:', id);
        return { id, name: 'John' };
    }
}

// MySQL 数据库实现
class MySQLService implements DatabaseService {
    async save(data: any): Promise<void> {
        console.log('保存到 MySQL:', data);
    }
    
    async findById(id: string): Promise<any> {
        console.log('从 MySQL 查询:', id);
        return { id, name: 'Jane' };
    }
}

现在我们有了多种实现:日志可以输出到控制台或文件,数据库可以是 MongoDB 或 MySQL。关键是,这些实现都遵循同样的接口。

第三步:业务服务只依赖接口

现在来实现我们的业务服务 UserService。注意,它只依赖接口,不依赖任何具体实现:

typescript 复制代码
// 使用依赖注入的业务服务
class UserService {
    constructor(
        private logger: Logger,           // 依赖 Logger 接口
        private database: DatabaseService // 依赖 DatabaseService 接口
    ) {}

    async createUser(userData: any): Promise<void> {
        this.logger.log('开始创建用户');
        
        try {
            await this.database.save(userData);
            this.logger.log('用户创建成功');
        } catch (error) {
            this.logger.log(`用户创建失败: ${error}`);
            throw error;
        }
    }

    async getUser(id: string): Promise<any> {
        this.logger.log(`查询用户: ${id}`);
        return await this.database.findById(id);
    }
}

UserService 不关心具体用的是哪种 Logger,也不关心用的是哪种数据库。它只需要知道"能打日志"和"能存储数据"就够了。

第四步:灵活组装,随心切换

现在,我们可以根据不同的需求,灵活地组装不同的服务组合:

typescript 复制代码
// 开发环境:使用控制台日志 + MongoDB
const devLogger = new ConsoleLogger();
const devDatabase = new MongoDBService();
const devUserService = new UserService(devLogger, devDatabase);

// 生产环境:使用文件日志 + MySQL
const prodLogger = new FileLogger();
const prodDatabase = new MySQLService();
const prodUserService = new UserService(prodLogger, prodDatabase);

// 测试环境:使用 Mock 对象
const mockLogger = new MockLogger();
const mockDatabase = new MockDatabase();
const testUserService = new UserService(mockLogger, mockDatabase);

这就是依赖注入的魅力:

  1. 解耦合 - UserService 不依赖具体的日志和数据库实现,只依赖抽象接口
  2. 可扩展 - 可以轻松替换不同的 Logger(控制台/文件)和 DatabaseService(MongoDB/MySQL)实现
  3. 易测试 - 可以注入 Mock 对象进行单元测试
  4. 灵活配置 - 同一个业务逻辑可以在不同环境下使用不同的底层服务实现

但是,你可能注意到了一个问题:每次创建 UserService,我们都需要手动创建并注入所有依赖。当依赖关系变得复杂时,这会变得非常繁琐。有没有办法自动化这个过程?

自动化DI:装饰器与容器

当应用规模扩大,手动管理依赖注入会变得非常繁琐。想象一下,如果你有几十个服务,每个服务又依赖其他几个服务,依赖关系会形成一张复杂的网。

这时候,我们需要一个**DI容器(DI Container)**来自动管理这些依赖关系。

什么是DI容器?

DI 容器的核心职责是:

  1. 注册(Register):记录服务标识符和具体实现之间的映射关系
  2. 解析(Resolve):当需要某个服务时,自动创建实例并注入所有依赖
  3. 生命周期管理:决定服务是单例还是每次都创建新实例

让我们从零开始实现一个简单但完整的 DI 容器。

第一步:定义核心类型

typescript 复制代码
// 构造函数类型
type Constructor<T = any> = new (...args: any[]) => T;

// 服务标识符:可以是类本身、字符串或Symbol
type ServiceIdentifier<T = any> = Constructor<T> | string | symbol;

// 服务生命周期
enum ServiceLifetime {
    Transient = 'transient', // 每次都创建新实例
    Singleton = 'singleton'  // 单例模式
}

// 服务描述符
interface ServiceDescriptor<T = any> {
    implementation: Constructor<T>;  // 具体实现类
    lifetime: ServiceLifetime;       // 生命周期
    dependencies: ServiceIdentifier[]; // 依赖的服务列表
}

// 依赖元数据
interface DependencyMetadata {
    index: number;           // 参数位置
    identifier: ServiceIdentifier; // 服务标识符
}

第二步:实现DI容器

typescript 复制代码
class DIContainer {
    // 存储服务注册信息
    private services = new Map<ServiceIdentifier, ServiceDescriptor>();
    
    // 缓存单例实例
    private singletonInstances = new Map<ServiceIdentifier, any>();
    
    // 解析栈,用于检测循环依赖
    private resolutionStack: ServiceIdentifier[] = [];

    // 注册瞬态服务(每次创建新实例)
    register<T>(identifier: ServiceIdentifier<T>, implementation: Constructor<T>): void {
        this.registerWithLifetime(identifier, implementation, ServiceLifetime.Transient);
    }

    // 注册单例服务(全局唯一实例)
    registerSingleton<T>(identifier: ServiceIdentifier<T>, implementation: Constructor<T>): void {
        this.registerWithLifetime(identifier, implementation, ServiceLifetime.Singleton);
    }

    // 统一的注册逻辑
    private registerWithLifetime<T>(
        identifier: ServiceIdentifier<T>,
        implementation: Constructor<T>,
        lifetime: ServiceLifetime
    ): void {
        // 分析构造函数的依赖
        const dependencies = this.getDependencies(implementation);
        this.services.set(identifier, { implementation, lifetime, dependencies });
    }

    // 解析服务:获取服务实例
    resolve<T>(identifier: ServiceIdentifier<T>): T {
        // 检测循环依赖
        if (this.resolutionStack.includes(identifier)) {
            throw new Error(`循环依赖: ${[...this.resolutionStack, identifier].join(' -> ')}`);
        }

        // 获取服务描述符
        const descriptor = this.services.get(identifier);
        if (!descriptor) {
            throw new Error(`服务未注册: ${String(identifier)}`);
        }

        // 加入解析栈
        this.resolutionStack.push(identifier);

        try {
            // 单例模式:使用缓存
            if (descriptor.lifetime === ServiceLifetime.Singleton) {
                if (!this.singletonInstances.has(identifier)) {
                    this.singletonInstances.set(identifier, this.createInstance(descriptor));
                }
                return this.singletonInstances.get(identifier);
            }
            
            // 瞬态模式:每次创建新实例
            return this.createInstance(descriptor);
        } finally {
            // 移出解析栈
            this.resolutionStack.pop();
        }
    }

    // 创建实例:递归解析所有依赖
    private createInstance<T>(descriptor: ServiceDescriptor<T>): T {
        // 递归解析所有依赖
        const resolvedDependencies = descriptor.dependencies.map(dep => this.resolve(dep));
        // 使用解析后的依赖创建实例
        return new descriptor.implementation(...resolvedDependencies);
    }

    // 获取类的依赖信息(通过装饰器添加的元数据)
    private getDependencies(target: Constructor): ServiceIdentifier[] {
        const metadata: DependencyMetadata[] = (target as any).__dependencies__ || [];
        return metadata.sort((a, b) => a.index - b.index).map(meta => meta.identifier);
    }
}

这个 DI 容器实现了三个核心功能:

  1. 循环依赖检测 :通过 resolutionStack 追踪解析链,防止无限递归
  2. 单例管理:对于单例服务,只创建一次并缓存
  3. 自动依赖解析:递归解析并创建所有依赖

第三步:定义装饰器

为了简化使用,我们定义两个装饰器:

typescript 复制代码
// @Injectable 装饰器:标记类可以被注入
function Injectable<T extends Constructor>(target: T): T {
    return target;
}

// @Inject 装饰器:标记构造函数参数需要注入的服务
function Inject(identifier: ServiceIdentifier) {
    return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
        // 在类的原型上记录依赖信息
        const metadata: DependencyMetadata[] = target.__dependencies__ || [];
        metadata.push({ index: parameterIndex, identifier });
        target.__dependencies__ = metadata;
    };
}

第四步:使用示例

现在,我们可以用装饰器来简化依赖注入的使用:

typescript 复制代码
// 定义唯一标识符(使用 Symbol 避免命名冲突)
const TOKENS = {
    LOGGER: Symbol('Logger'),
    DATABASE: Symbol('Database'),
    USER_SERVICE: Symbol('UserService')
};

// 定义服务
@Injectable
class Logger {
    log(message: string): void {
        console.log(`[LOG] ${message}`);
    }
}

@Injectable
class Database {
    // 使用 @Inject 指定要注入的服务
    constructor(@Inject(TOKENS.LOGGER) private logger: Logger) {}

    async save(data: any): Promise<void> {
        this.logger.log(`保存数据: ${JSON.stringify(data)}`);
    }
}

@Injectable
class UserService {
    constructor(
        @Inject(TOKENS.DATABASE) private database: Database,
        @Inject(TOKENS.LOGGER) private logger: Logger
    ) {}

    async createUser(userData: any): Promise<any> {
        this.logger.log('创建用户');
        await this.database.save(userData);
        return { id: Date.now(), ...userData };
    }
}

// 注册服务
const container = new DIContainer();
container.registerSingleton(TOKENS.LOGGER, Logger);     // Logger 是单例
container.register(TOKENS.DATABASE, Database);           // Database 每次创建新实例
container.register(TOKENS.USER_SERVICE, UserService);

// 使用服务(所有依赖自动注入)
const userService = container.resolve<UserService>(TOKENS.USER_SERVICE);
userService.createUser({ name: 'John', email: 'john@example.com' });

现在,依赖注入变得非常简洁:

  1. @Injectable 标记类
  2. @Inject(TOKENS.XXX) 标记依赖
  3. 注册到容器
  4. 容器自动解析所有依赖并创建实例

关键概念Q&A

Q:registerregisterSingleton 有什么区别?

A:生命周期不同。registerSingleton 注册的是单例,不管多少个类依赖它,使用的总是同一个实例,适合全局唯一的资源(如日志服务、配置服务)。register 注册的不是单例,每次有类依赖时,都会创建一个全新的实例。

Q:DI 注册的 class 是在什么阶段实例化的?

A:在 register 阶段,DI 容器仅仅记录了 class 和标识符之间的映射关系,以及构造函数依赖的元数据(__dependencies__),并没有实例化。只有在调用 resolve 时,才会实例化该类。如果此类依赖其他类,会深度递归解析所有依赖并逐个实例化。这是 DI 容器**延迟实例化(Lazy Instantiation)**的特性,可以提高启动性能。

VSCode的DI系统:工业级实现

前面我们学习了依赖注入的基本原理和实现。现在让我们看看,VSCode 这样一个拥有数百万行代码的大型项目,是如何应用依赖注入系统的。

VSCode DI 的使用方式

VSCode 使用了自定义的依赖注入系统,使用起来非常优雅。让我们通过一个真实的例子来看看:

第一步:创建服务标识符

typescript 复制代码
// 来源:src/vs/workbench/services/textfile/common/textfiles.ts
// 使用 createDecorator 创建服务标识符
const ITextFileService = createDecorator<ITextFileService>('textFileService');

第二步:全局注册单例

typescript 复制代码
// 来源:src/vs/workbench/services/textfile/electron-sandbox/nativeTextFileService.ts
// 注册为单例服务
registerSingleton(ITextFileService, NativeTextFileService, InstantiationType.Eager);

第三步:在其他服务中使用

typescript 复制代码
// 在任何需要文件服务的地方,直接通过装饰器注入
class XXXService {
    constructor(
        @ITextFileService private readonly _textFileService: ITextFileService 
    ) {}
    
    someMethod() {
        // 直接使用注入的服务
        this._textFileService.save(/* ... */);
    }
}

是不是非常简洁?只需要三步:

  1. createDecorator 创建服务标识符
  2. registerSingleton 全局注册
  3. 在构造函数中用装饰器注入

这背后有三个关键的实现。

关键实现一:createDecorator

createDecorator 函数用于创建服务标识符和对应的装饰器:

typescript 复制代码
// 来源:src/vs/platform/instantiation/common/instantiation.ts

export namespace _util {
    export const serviceIds = new Map<string, ServiceIdentifier<any>>();
    export const DI_TARGET = '$di$target';
    export const DI_DEPENDENCIES = '$di$dependencies';

    export function getServiceDependencies(ctor: any): { id: ServiceIdentifier<any>; index: number }[] {
        return ctor[DI_DEPENDENCIES] || [];
    }
}

export interface ServiceIdentifier<T> {
    (...args: any[]): void;
    type: T;
}

// 在类原型上存储依赖信息
function storeServiceDependency(id: Function, target: Function, index: number): void {
    if ((target as any)[_util.DI_TARGET] === target) {
        (target as any)[_util.DI_DEPENDENCIES].push({ id, index });
    } else {
        (target as any)[_util.DI_DEPENDENCIES] = [{ id, index }];
        (target as any)[_util.DI_TARGET] = target;
    }
}

// 创建服务标识符和装饰器
export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
    // 避免重复创建
    if (_util.serviceIds.has(serviceId)) {
        return _util.serviceIds.get(serviceId)!;
    }

    // 返回一个装饰器函数
    const id = <any>function (target: Function, key: string, index: number) {
        if (arguments.length !== 3) {
            throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
        }
        // 在类的原型上记录依赖关系
        storeServiceDependency(id, target, index);
    };

    id.toString = () => serviceId;

    _util.serviceIds.set(serviceId, id);
    return id;
}

createDecorator 的作用是生成一个装饰器,当装饰器被使用时,会在被装饰的类原型上挂载 DI_TARGETDI_DEPENDENCIES 属性,用于后续的依赖分析。

关键实现二:registerSingleton

registerSingleton 用于全局注册服务:

typescript 复制代码
// 来源:src/vs/platform/instantiation/common/extensions.ts

// 全局注册表
const _registry: [ServiceIdentifier<any>, SyncDescriptor<any>][] = [];

// 实例化类型
export const enum InstantiationType {
    Eager = 0,    // 立即实例化
    Delayed = 1   // 延迟实例化
}

// 注册单例服务
export function registerSingleton<T, Services extends BrandedService[]>(
    id: ServiceIdentifier<T>, 
    ctorOrDescriptor: { new(...services: Services): T } | SyncDescriptor<any>, 
    supportsDelayedInstantiation?: boolean | InstantiationType
): void {
    if (!(ctorOrDescriptor instanceof SyncDescriptor)) {
        ctorOrDescriptor = new SyncDescriptor<T>(
            ctorOrDescriptor as new (...args: any[]) => T, 
            [], 
            Boolean(supportsDelayedInstantiation)
        );
    }

    // 记录到全局注册表
    _registry.push([id, ctorOrDescriptor]);
}

// 获取所有已注册的服务
export function getSingletonServiceDescriptors(): [ServiceIdentifier<any>, SyncDescriptor<any>][] {
    return _registry;
}

register 阶段,服务只是被记录到全局注册表 _registry 中,并没有被实例化。实例化发生在第一次使用时。

关键实现三:InstantiationService

InstantiationService 是 VSCode DI 系统的核心,负责实际的依赖解析和实例创建:

typescript 复制代码
// 来源:src/vs/platform/instantiation/common/instantiationService.ts

export class InstantiationService implements IInstantiationService {
    constructor(
        private readonly _services: ServiceCollection = new ServiceCollection(),
    ) {
        // 把自己也注册到容器中,这样其他服务也可以依赖 InstantiationService
        this._services.set(IInstantiationService, this);
    }

    createInstance<T>(ctor: any, ...args: any[]): T {
        // 获取构造函数的依赖
        const serviceDependencies = _util.getServiceDependencies(ctor)
            .sort((a, b) => a.index - b.index);
        
        const serviceArgs: any[] = [];
        
        // 解析每个依赖
        for (const dependency of serviceDependencies) {
            const service = this._getOrCreateServiceInstance(dependency.id);
            serviceArgs.push(service);
        }

        // 使用 Reflect.construct 创建实例
        return Reflect.construct<any, T>(ctor, serviceArgs);
    }

    private _getOrCreateServiceInstance(id: ServiceIdentifier<any>): any {
        // 如果已经创建过,直接返回
        // 否则创建新实例(递归解析依赖)
        // ...
    }
}

createInstance 方法的核心逻辑:

  1. 通过 getServiceDependencies 获取要创建类的依赖列表
  2. 递归解析每个依赖,调用 _getOrCreateServiceInstance 获取依赖实例
  3. 使用 Reflect.construct 创建目标实例,把所有依赖作为构造函数参数传入

DI 系统的初始化

那么,VSCode 的 DI 系统是在哪里启动的呢?答案是在应用的入口文件:

typescript 复制代码
// 来源:src/vs/code/electron-main/main.ts

class CodeMain {
    private createServices() {
        const services = new ServiceCollection();
        
        // 手动创建一些基础服务
        const environmentMainService = new EnvironmentMainService(xxx);
        services.set(IEnvironmentMainService, environmentMainService);
        
        // ... 其他基础服务
        
        // 创建 InstantiationService
        return [new InstantiationService(services, true)];
    }
}

你可能会疑惑:为什么有些服务(如 EnvironmentMainService)还是手动 new 创建的,而不是通过 DI 系统自动创建?

这是因为:

  1. DI 系统本身无法自举:必须先有第一批手动创建的服务,才能启动 DI 系统
  2. 基础服务的特殊性:一些核心服务(如环境服务、配置服务)是 DI 系统本身的依赖,必须先手动创建
  3. 初始化顺序控制:某些服务的初始化顺序很重要,手动创建可以精确控制

等基础服务启动完成后,后续的服务就可以利用 DI 系统自动实例化了。

ServiceCollection:简单的服务容器

typescript 复制代码
// 来源:src/vs/platform/instantiation/common/serviceCollection.ts

export class ServiceCollection {
    private _entries = new Map<ServiceIdentifier<any>, any>();

    constructor(...entries: [ServiceIdentifier<any>, any][]) {
        for (const [id, service] of entries) {
            this.set(id, service);
        }
    }

    set<T>(id: ServiceIdentifier<T>, instanceOrDescriptor: T | SyncDescriptor<T>): T | SyncDescriptor<T> {
        const result = this._entries.get(id);
        this._entries.set(id, instanceOrDescriptor);
        return result;
    }

    has(id: ServiceIdentifier<any>): boolean {
        return this._entries.has(id);
    }

    get<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
        return this._entries.get(id);
    }
}

ServiceCollection 的实现非常简单,就是一个 Map,用于存储服务标识符和服务实例(或描述符)之间的映射关系。

总结

依赖注入的核心思想很简单:不要在类内部创建依赖,而是从外部传入。这个简单的改变带来了解耦、易测试、可扩展等诸多好处。

我们从三个层次理解了依赖注入:手动注入展示了基本概念,DI 容器实现了自动化管理,VSCode 则演示了工业级的应用实践。关键技术包括装饰器、服务容器、延迟实例化和循环依赖检测。

依赖注入不是银弹,但当你的代码库变得复杂,类之间的依赖关系让你焦头烂额时,它确实是让代码变得更优雅、更易维护的重要工具。

写在最后

依赖注入是我在阅读 VSCode 源码时最先注意到的设计模式之一。起初看到到处都是 @IXXXService 装饰器时,我还有些困惑。但深入理解后,我被它的优雅深深折服:整个应用的几百个服务,就像积木一样可以自由组合,每个服务只关注自己的职责,依赖关系清晰明了。

留给你的思考题

写完这篇文章,我想问问你:

  • 你的项目中是否也遇到过"到处都是 new,依赖关系一团乱麻"的问题?
  • 你是如何解决的?是引入了 DI 框架,还是有其他更好的方案?
  • 对于小型项目,你觉得依赖注入是不是"过度设计"?

交流与分享

如果你对 VSCode 的架构设计感兴趣,欢迎关注我的 VSCode 源码寻宝:那些藏在代码里的设计智慧 专栏,我会持续分享 VSCode 源码中的精彩设计。

最后,如果你在实践中遇到了问题,或者有更好的实践经验,欢迎在评论区分享。期待你的见解:

  • 你在引入 DI 时遇到过哪些坑?
  • 你的团队是如何平衡"灵活性"和"复杂度"的?
  • 有没有什么"不适合用 DI"的场景?
相关推荐
Henrii_历小海9 分钟前
WhatsApp Web 不是手机镜像:多设备协议、本地数据流与出海获客流水线
前端·智能手机
ayqy贾杰16 分钟前
Claude Fable 5 提示词泄漏,抓紧学习下
前端·后端·面试
To_OC8 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志9 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯11 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能