依赖注入

为了让模块开发者能够专注于自己的模块,无需关注其他模块的实现细节,我们使用 @opensumi/di 来实现依赖的解耦。

使用

在依赖注入的编码模式下,我们如果想使用一个模块内的服务,不再需要依赖其具体实现,而只需要依赖其显示声明的 Token 即可。

以在模块中使用弹窗服务能力 IDialogService 为例:

import { IDialogService } from '@opensumi/ide-overlay';

@Injectable()
class DemoService {
  @Autowired(IDialogService)
  private readonly dialogService: IDialogService;

  run() {
    this.dialogService.info('Hello OpenSumi');
  }
}

进一步的,我们还可以通过将部分 Token 显示的声明在 @opensumi/ide-core-browser@opensumi/ide-core-common@opensumi/ide-core-node 中,来实现对服务能力的非直接依赖,减少循环依赖问题。

注册自定义服务

在 OpenSumi 框架中,针对 BrowserNode 我们设计了各自的模块定义形式,分别为:

  • BrowserModule
  • NodeModule

我们在模块导出时便可以随时在上面挂载自定义服务,前端与后端模块的代码在导出内容上基本是一致的,下面以前端模块为例,注册一个自定义服务的代码如下所示:

import { Injectable } from '@opensumi/di';
import { BrowserModule } from '@opensumi/ide-core-browser';

export interface IDemoService {
  run(): void;
}

export const IDemoService = Symbol('IDemoService');

@Injectable()
export class DemoService {
  run() {
    console.log('Hello OpenSumi');
  }
}

@Injectable()
export class DemoModule extends BrowserModule {
  providers = [
    {
      token: IDemoService,
      useClass: DemoService
    }
  ];
}

除了 useClass 的定义语法,常用的语法还有 useFactory, useValue 使用方法如下所示:

export class DemoModule extends BrowserModule {
  providers = [
    {
      token: IDemo2Service,
      useValue: {
        run: () => {
          console.log('Hello OpenSumi');
        }
      }
    },
    {
      token: IDemoService,
      useFactory: (injector: Injector) => {
        // 这里可以直接获取到 injector 实例
        // 通过这种方式,我们可以为多个 Token 挂载同个实现来做到服务职责的分离
        return injector.get(IDemo2Service);
      }
    }
  ];
}

进一步能力

实现多例

在声明模块时,我们可以通过在模块的依赖注入配置中传入 { multiple: true } 来让服务的实现变为多例,即每次通过 DI 获取到的服务都是重新初始化出来的实例,实例代码如下:

@Injectable({ multiple: true })
class DemoService {
  @Autowired(IDialogService)
  private readonly dialogService: IDialogService;

  run() {
    this.dialogService.info('Hello OpenSumi');
  }
}

创建子容器

通过 @opensumi/di 创建子容器的能力,我们还可以通过直接获取 Injector 示例来实现自定义多例等丰富多彩的功能。

import { Injectable, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
export const IDemoService = Symbol('IDemoService');
@Injectable()
class DemoService {
  static createContainer(injector: Injector, message: string): Injector {
    const child = injector.createChild([
      {
        token: IDemoService,
        useValue: () => {
          console.log(`Hello ${message}`);
        }
      }
    ]);
    return child;
  }
  @Autowired(INJECTOR_TOKEN)
  private readonly injector: Injector;

  @Autowired(IDemoService)
  private readonly demoService: IDemoService;

  createChild() {
    return DemoService.createContainer(this.injector, 'OpenSumi');
  }
}

分类服务

通过在创建 DI 子容器中为服务注册声明 tag,我们就可以通过 tag 参数来实现对不同分类下服务的调用,通常我们用于一些需要有特定分类的服务调用上,如在配置模块中,通过 tag 对同一个 Token 注册了三个不同的实现:

export function injectFolderPreferenceProvider(inject: Injector): void {
  inject.addProviders({
    token: FolderPreferenceProviderFactory,
    useFactory: () => {
      return (options: FolderPreferenceProviderOptions) => {
        const configurations = inject.get(PreferenceConfigurations);
        const sectionName = configurations.getName(options.configUri);
        const child = inject.createChild(
          [
            {
              token: FolderPreferenceProviderOptions,
              useValue: options
            }
          ],
          {
            dropdownForTag: true,
            tag: sectionName
          }
        );
        // 当传入为配置文件时,如settings.json, 获取Setting
        if (configurations.isConfigUri(options.configUri)) {
          child.addProviders({
            token: FolderPreferenceProvider,
            useClass: FolderPreferenceProvider
          });
          return child.get(FolderPreferenceProvider);
        }
        // 当传入为其他文件时,如launch.json
        // 需设置对应的FolderPreferenceProvider 及其对应的 FolderPreferenceProviderOptions 依赖
        // 这里的FolderPreferenceProvider获取必须为多例,因为工作区模式下可能存在多个配置文件
        return child.get(FolderPreferenceProvider, {
          tag: sectionName,
          multiple: true
        });
      };
    }
  });
}

详细实现见:preferences/src/browser/index.ts

更多能力,请自行查阅 @opensumi/di 文档。