VSCode 源码解读-Workbench


前言

上一篇文章中提到,VS Code 中有许多服务(Services),分别提供不同模块的 API 以便其他模块调用,在需要依赖该服务的类构造函数中以 Decorator 修饰参数的形式声明依赖,调用者不需要显式的 new 这个服务,在调用者被创建时,这些依赖的服务也会被自动创建并传递给该调用者,同时不同的服务也可以相互依赖。这可以极大地降低程序耦合性,同时提高可维护性。

这种解耦程序的方式被称为 **依赖注入(DI),**是 **控制反转(IOC)**的一种实现方式。即对象(或实体)的依赖(可以是其他对象)通过外部注入,避免对象内部自身实现依赖的实例化过程。依赖注入在软件工程中有大量的实践应用,常见的服务端框架如 Spring、Laravel 等都是基于依赖注入的方式来管理对象之间的依赖关系。需要注意的是控制反转是一种设计思想,而依赖注入是这种思想的具体实现。

依赖注入的基本原理是由外部的容器来保存对象之间的依赖关系,同时这些对象的实例化也由容器来实现,这个容器被称为**依赖注入容器。**实际应用中我们的系统可能存在大量的对象或者服务,对象之间的依赖关系非常复杂,比如 B 依赖 A,则 A 就要先于 B 被实例化,这就要求外部的容器能够分析出这些对象的依赖关系。许多依赖注入框架还支持多种注入方式,比如构造函数注入、属性注入、方法参数注入等。

虽然依赖注入常见于传统后端框架,但作为一种设计模式,其适用于大部分基于面向对象思想构建的系统中。Google 出品的前端框架 Angular、今年大热的基于 TypeScript 的 Node.js 框架 nest.js 都大量使用了依赖注入实现程序的解耦,也有跨端(前端+ Node.js )的控制反转框架 InversifyJS ,同样在 VS Code 中也实现了一个轻量级的依赖注入模式,本文主要从源码出发详细剖析 VS Code 依赖注入的实现原理(VS Code 中主要使用构造函数注入)。

基本用法

首先需要定义一个类并在其构造函数中声明依赖的服务

class MyClass {
	constructor(
		@IAuthService private readonly authService: IAuthService,
		@IStorageService private readonly storageService: IStorageService,
	) {
	}
}

构造函数中的 @IAuthService 和 @IStorageService 是两个 Decorator(装饰器),装饰器在 JavaScript 中还属于一项提案,在 TypeScript 中是一项实验性特性。它可以被附加到类声明、方法、访问符以及参数上,在这段代码中他们被附加到了 MyClass 的构造函数参数 authService 和 storageService 上,也就是参数装饰器。参数装饰器会在运行时被函数调用,并传入三个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  2. 成员的名字
  3. 参数在函数参数列表中的索引

服务的 Decorator 和接口定义一般如下

// 创建装饰器
export const IAuthService = createDecorator<IAuthService>('AuthService');
// 接口
export interface IAuthService {
	readonly id: string;
	readonly nickName: string;
	readonly firstName: string;
	readonly lastName: string;
  requestService: IRequestService;
}

服务接口需要有具体实现,同时也允许依赖其他服务

class AuthServiceImpl implements IAuthService {
  constructor(
		@IRequestService public readonly requestService IRequestService,		
  ){
  }

  public async getUserInfo() {
		const { id, nickName, firstName } = await getUserInfo();
		this.id = id;
		this.nickName = nickName;
		this.firstName = firstName;
		//...
	} 
}

还需要一个服务集,用于保存一组服务,并用其来创建一个容器

// 服务集
export class ServiceCollection {

	private _entries = new Map<ServiceIdentifier<any>, any>();

	constructor(...entries: [ServiceIdentifier<any>, any][]) {
		for (let [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;
	}

	forEach(callback: (id: ServiceIdentifier<any>, instanceOrDescriptor: any) => any): void {
		this._entries.forEach((value, key) => callback(key, value));
	}

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

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