ioc 设计 ---- 注册服务
上次,我们讲到了「定义服务」,这里,我们开始解读注册服务部分。
注册的过程,其实就是将服务放入服务池里,vscode 里有两种方式放入,这里,我们讲最直接的,直接放入集合。
首先,我们需要一个服务集合:
const services = new ServiceCollection();
// serviceCollection
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor } from './descriptors';
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;
}
has(id: ServiceIdentifier<any>): boolean {
return this._entries.has(id);
}
get<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
return this._entries.get(id);
}
}
基于此处,我们要开始讲解:ServiceIdentifier、SyncDescriptor 了。
ServiceIdentifier
/**
* Identifies a service of type T
*/
export interface ServiceIdentifier<T> {
(...args: any[]): void;
type: T;
}
上面是一个ServiceIdentifier ,有点难看懂,没关系,我们结合下具体的使用,前面,我们有复制到如下内容:
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): any {
if (arguments.length !== 3) {
throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
}
storeServiceDependency(id, target, index, false);
};
id.toString = () => serviceId;
_util.serviceIds.set(serviceId, id);
return id;
}
返回值是一个:ServiceIdentifier, 即 id 这个东东, 而我们又是按照如下方式使用的createDecorator。
export const ITestService = createDecorator<ITestService>('testService');
所以,结合起来,我们看,id 本身是一个函数, 其实就是 ts的属性参数装饰器函数,并且重写了其toString方法,即 id 这个函数,在使用id.toString()时候,返回服务名:testService, 但我们看到,在使用ServiceIdentifier的时候,其实并没有明面上去定义type 这个字段,我们通过demo 来测试下。
export interface ServiceIdentifier<T> {
(...args: any[]): void;
type: T;
}
export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
const id = () => {
console.log('hello, here is id');
}
id.toString = () => serviceId;
return id;
}
interface ITestService {
test: () => void;
}
class TestService implements ITestService {
test() {
console.log('hello');
}
}
const id = createDecorator<ITestService>('hello');
id();
console.log('toString:', id.toString());
console.log('type:', id.type);
发现,如果我们不明确的去定义:type的话,会有 tslint报错,那vscode是怎么搞的。其实我们发现,在定义的时候:
const id = <any>function (target: Function, key: string, index: number): any
这里,有any,其实就是转换成了 any 。我们也这样,然后测试一波。
export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
const id = <any>function() {
console.log('hello, here is id');
}
id.toString = () => serviceId;
return id;
}
➜ my-vscode git:(feat-day-05) ✗ yarn test
yarn run v1.22.4
warning package.json: No license field
$ ts-node ./src/test.ts
hello, here is id
toString: hello
type: undefined
额,我们发现,这个type不赋值就没东西的,算了,后面遇到的话再看是在哪里赋值的,先记个 TODO。
总结
ServiceIdentifier就是返回了一个typescript的参数装饰器函数,并且重写了 toString 方法,让其返回服务的名字即 id。
SyncDescriptor
我们在注册服务的时候,如果将所有的服务立即实例化,则在项目较大的时候,会有性能问题,因此,vscode 在这里有一个优化,就是延迟实例化,即一个思想:idle-until-urgent,除非立即需要,否则等待空闲时间再去实例化。因此,很多时候,我们不需要服务立即实例化,但我们也得先把服务注册上。因此,在服务集合里包含两种类型。
- 服务实例本身,即被使用过的服务实例则会一直存在集合里。
- 服务装饰器,可以看作是服务的一个占位,表示在集合里有这个服务若要用到此服务,有以下步骤:
- 则根据装饰器描述的内容去把服务实例化
- 将服务装饰器替换为服务实例
- 后续再次调用的时候,则为服务实例本身
我们来看一下具体结构:
export class SyncDescriptor<T> {
readonly ctor: any; // 服务的构造函数
readonly staticArguments: any[]; // 服务初始化需要的静态参数
readonly supportsDelayedInstantiation: boolean; // 是否允许此对象延迟实例化,这个属性目前还没理解怎么用的,先记个TODO
constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) {
this.ctor = ctor;
this.staticArguments = staticArguments;
this.supportsDelayedInstantiation = supportsDelayedInstantiation;
}
}
这里可能有个疑问:
staticArguments服务初始化的静态参数我理解,就是一些初始化时候需要的参数,但是如果初始化的时候需要的是一个服务呢,该怎么办?
这个大家其实不用担心,后面实例化的时候会讲解到,大家可以先只知道是初始化需要的参数即可,
总结
通过上述结构,我们其实知道:SyncDescriptor就是描述了一个类实例化的时候需要的信息的一个东西,在后续真正实例化的时候,需要通过它去实例化对象。
服务集合-ServiceCollection
了解了上述概念,我们再来看服务集合。
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor } from './descriptors';
export class ServiceCollection {
private _entries = new Map<ServiceIdentifier<any>, any>(); // 存储服务的 ma p
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;
}
// 检测是否已经存在
has(id: ServiceIdentifier<any>): boolean {
return this._entries.has(id);
}
// 获得一个服务实例或者服务装饰器
get<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
return this._entries.get(id);
}
}
看起来,现在看的明明白白了。
Startup
那我们继续startup函数
private async startup(args: NativeParsedArgs): Promise<void> {
const bufferLogService = new BufferLogService();
const [instantiationService, instanceEnvironment, environmentService] = this.createServices(
args,
bufferLogService);
}
这里是创建了一个bufferLogService,这个就不展开了,感兴趣可以自己看这个服务干啥的,不影响主流程,主要是:createServices, 这里面注册了很多服务,我只列举关键的来讲解这个流程,其余先省略。
private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService] {
const services = new ServiceCollection();
...
const environmentService = new EnvironmentMainService(args);
services.set(IEnvironmentMainService, environmentService);// 服务实例本身
...
services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); // 装饰器
...
services.set(IProductService, { _serviceBrand: undefined, ...product }); // 服务实例本身第二种写法
return [new InstantiationService(services, true), instanceEnvironment, environmentService];
}
可以看到,创建了服务集合ServiceCollection并将其全部放入:new InstantiationService(services, true), 这里在服务集合里也放了两种类型,一个是:服务实例本身;一个是装饰器。其中有一个IProductService比较偷懒,没写类,直接构造了个对象传递进去。
其实到这里,最重要的就是new InstantiationService(services, true), 这个主要去管理和实例化服务的地方,也是 IOC 机制的关键类。
今天先到这里了,上班咯!