├── .gitignore ├── tsconfig.eslint.json ├── tsconfig.dev.json ├── .eslintrc.js ├── src ├── typings │ └── type.d.ts ├── descriptors.ts ├── extension.ts ├── serviceCollection.ts ├── index.ts ├── instantiation.ts ├── graph.ts └── instantiationService.ts ├── tsconfig.json ├── test ├── unit │ ├── instantiation.test.ts │ ├── extension.test.ts │ ├── serviceCollection.test.ts │ └── graph.test.ts └── intergration │ └── instantiationService.test.ts ├── webpack.config.js ├── package.json ├── README.md └── README.CN.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "**/*.ts" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | }, 5 | "extends": [], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": 12, 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "@typescript-eslint" 13 | ], 14 | "rules": { 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/typings/type.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | export type Ctor = new (...args: any[]) => T; 7 | 8 | export interface ServicesAccessor { 9 | get(id: ServiceIdentifier): T; 10 | } 11 | 12 | export interface ServiceIdentifier { 13 | (target: Ctor, key: string, index: number): void; 14 | toString(): string; 15 | } 16 | 17 | 18 | export interface ServicesAccessor { 19 | get(id: ServiceIdentifier): T; 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", // 为了让 Jest 支持 Optional Chaining 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "noImplicitAny": true, 9 | "declaration": true, 10 | "jsx": "react", 11 | "experimentalDecorators": true, // 为了让其支持装饰器语法 12 | "declarationDir": "dist", 13 | "baseUrl": "./", 14 | "paths": { 15 | "@/*": ["./src/*"] 16 | }, 17 | "resolveJsonModule": true 18 | }, 19 | "exclude": [] 20 | } 21 | -------------------------------------------------------------------------------- /src/descriptors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { Ctor } from './typings/type'; 7 | 8 | /* 用于包裹构造函数,延迟初始化类 */ 9 | export class SyncDescriptor { 10 | readonly ctor: Ctor; 11 | readonly staticArguments: any[]; 12 | readonly supportsDelayedInstantiation: boolean; 13 | 14 | constructor( 15 | ctor: Ctor, 16 | staticArguments: any[] = [], 17 | supportsDelayedInstantiation = false, 18 | ) { 19 | this.ctor = ctor; 20 | this.staticArguments = staticArguments; 21 | this.supportsDelayedInstantiation = supportsDelayedInstantiation; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { SyncDescriptor } from './descriptors'; 7 | import { Ctor, ServiceIdentifier } from './typings/type'; 8 | /* eslint-disable no-underscore-dangle */ 9 | 10 | const _registry: [ServiceIdentifier, SyncDescriptor][] = []; 11 | 12 | export function registerSingleton( 13 | id: ServiceIdentifier, 14 | ctor: Ctor, 15 | supportsDelayedInstantiation?: boolean, 16 | ): void { 17 | _registry.push([id, new SyncDescriptor(ctor, [], supportsDelayedInstantiation)]); 18 | } 19 | 20 | export function getSingletonServiceDescriptors(): [ServiceIdentifier, SyncDescriptor][] { 21 | return _registry; 22 | } 23 | -------------------------------------------------------------------------------- /test/unit/instantiation.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable no-useless-constructor */ 3 | import { createDecorator, _util } from '../../src/instantiation'; 4 | 5 | describe('unit: instantiation', () => { 6 | test('decorator', () => { 7 | const decoratorA = createDecorator('decoratorA'); 8 | expect(_util.serviceIds.get('decoratorA')).toEqual(decoratorA); 9 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class 10 | class B { 11 | constructor(@decoratorA a: any) {} 12 | } 13 | 14 | expect(_util.getServiceDependencies(B)[0].id.toString()).toBe('decoratorA'); 15 | expect((B as any)[_util.DI_TARGET]).toBe(B); 16 | expect((B as any)[_util.DI_DEPENDENCIES]).toBe(_util.getServiceDependencies(B)); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/extension.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | registerSingleton, 3 | getSingletonServiceDescriptors, 4 | } from '../../src/extension'; 5 | import { createDecorator } from '../../src/instantiation'; 6 | import { SyncDescriptor } from '../../src/descriptors'; 7 | 8 | describe('unit: extension', () => { 9 | test('registerSingleton/getSingletonServiceDescriptors', () => { 10 | class A { 11 | echo() { 12 | return 'A'; 13 | } 14 | } 15 | 16 | let singletons = getSingletonServiceDescriptors(); 17 | expect(singletons.length).toBe(0); 18 | 19 | const serviceA = createDecorator('A'); 20 | registerSingleton(serviceA, A); 21 | 22 | singletons = getSingletonServiceDescriptors(); 23 | expect(singletons.length).toBe(1); 24 | const [id, descriptors] = singletons[0]; 25 | expect(id).toBe(serviceA); 26 | expect(descriptors instanceof SyncDescriptor).toBeTruthy(); 27 | expect(descriptors.ctor).toBe(A); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | const path = require('path'); 7 | module.exports = { 8 | mode: 'development', 9 | devtool: 'source-map', 10 | entry: './src/index.ts', 11 | watch: true, 12 | output: { 13 | path: path.resolve(__dirname, './dist'), 14 | filename: 'index.js', 15 | libraryTarget: 'umd', 16 | library: 'UserAgent', 17 | umdNamedDefine: true 18 | }, 19 | resolve: { 20 | extensions: ['.ts', '.js'] 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.ts$/, 26 | use: [ 27 | { 28 | loader: 'babel-loader', 29 | options: { 30 | presets: ['@babel/preset-env'], 31 | plugins: ['@babel/plugin-transform-runtime'] 32 | } 33 | }, 34 | { 35 | loader: 'ts-loader' 36 | } 37 | ] 38 | }, 39 | ] 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/serviceCollection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { ServiceIdentifier } from './typings/type'; 7 | import { SyncDescriptor } from './descriptors'; 8 | 9 | export class ServiceCollection { 10 | private entries = new Map, SyncDescriptor | any>(); 11 | constructor(...entries: [ServiceIdentifier, SyncDescriptor | any][]) { 12 | for (const [id, service] of entries) { 13 | this.set(id, service); 14 | } 15 | } 16 | 17 | set( 18 | id: ServiceIdentifier, 19 | instanceOrDescriptor: T | SyncDescriptor, 20 | ): T | SyncDescriptor { 21 | const result = this.entries.get(id); 22 | this.entries.set(id, instanceOrDescriptor); 23 | return result; 24 | } 25 | 26 | has(id: ServiceIdentifier): boolean { 27 | return this.entries.has(id); 28 | } 29 | 30 | get(id: ServiceIdentifier): T | SyncDescriptor { 31 | return this.entries.get(id); 32 | } 33 | 34 | forEach(callback: ( 35 | id: ServiceIdentifier, 36 | instanceOrDescriptor: SyncDescriptor | any 37 | ) => any): void { 38 | this.entries.forEach((value, key) => { 39 | callback(key, value); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/unit/serviceCollection.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-extraneous-class */ 2 | import { ServiceCollection } from '../../src/serviceCollection'; 3 | import { createDecorator } from '../../src/instantiation'; 4 | import { SyncDescriptor } from '../../src/descriptors'; 5 | 6 | describe('unit: serviceCollection', () => { 7 | test('ServiceCollection', () => { 8 | class A {} 9 | 10 | class B {} 11 | 12 | const serviceA = createDecorator('A'); 13 | const serviceB = createDecorator('B'); 14 | const serviceC = createDecorator('C'); 15 | const syncDescriptorA = new SyncDescriptor(A); 16 | const bInstance = new B(); 17 | 18 | const serviceCollection = new ServiceCollection(); 19 | serviceCollection.set(serviceA, syncDescriptorA); 20 | serviceCollection.set(serviceB, bInstance); 21 | 22 | expect(serviceCollection.get(serviceA)).toEqual(syncDescriptorA); 23 | expect(serviceCollection.get(serviceB)).toEqual(bInstance); 24 | expect(serviceCollection.has(serviceA)).toBeTruthy(); 25 | expect(serviceCollection.has(serviceB)).toBeTruthy(); 26 | expect(serviceCollection.has(serviceC)).toBeFalsy(); 27 | serviceCollection.forEach((id, instanceOrDescriptor) => { 28 | if (id === serviceA) { 29 | expect(instanceOrDescriptor).toEqual(syncDescriptorA); 30 | } 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { createDecorator, _util } from './instantiation'; 7 | import { InstantiationService } from './instantiationService'; 8 | import { ServiceCollection } from './serviceCollection'; 9 | import { SyncDescriptor } from './descriptors'; 10 | 11 | class A { 12 | echo() { 13 | return 'A'; 14 | } 15 | } 16 | 17 | class B { 18 | echo() { 19 | return 'B'; 20 | } 21 | } 22 | 23 | class C { 24 | echo() { 25 | return 'C'; 26 | } 27 | } 28 | 29 | // 装饰器 30 | const serviceA = createDecorator('A'); 31 | const serviceB = createDecorator('B'); 32 | const serviceC = createDecorator('C'); 33 | 34 | class D { 35 | private leftText: string; 36 | private rightText: string; 37 | constructor(@serviceA private a: A, @serviceB private b: B, @serviceC private c: C, leftText = '', rightText = '') { 38 | this.leftText = leftText; 39 | this.rightText = rightText; 40 | } 41 | 42 | echo() { 43 | return `${this.a.echo()}${this.b.echo()}${this.c.echo()}${this.leftText}${this.rightText}`; 44 | } 45 | } 46 | 47 | const aInstance = new A(); 48 | const bInstance = new B(); 49 | const svrsCollection = new ServiceCollection(); 50 | svrsCollection.set(serviceA, aInstance); 51 | svrsCollection.set(serviceB, bInstance); 52 | svrsCollection.set(serviceC, new SyncDescriptor(C)); 53 | 54 | const instantiationService = new InstantiationService(svrsCollection); 55 | const dInstance = instantiationService.createInstance(D, 'L', 'R') as D; 56 | console.log(instantiationService); 57 | console.log(dInstance); 58 | const echoText = dInstance.echo(); 59 | console.log(echoText); 60 | 61 | instantiationService.invokeFunction((accessor) => { 62 | console.log(accessor); 63 | const bInstance2 = accessor.get(serviceA); 64 | }); -------------------------------------------------------------------------------- /test/unit/graph.test.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from '../../src/graph'; 2 | 3 | describe('unit: graph', () => { 4 | test('lookup/lookupOrInsertNode/isEmpty', () => { 5 | const graph = new Graph((data: any) => data.id.toString()); 6 | const data = { 7 | id: { toString: () => 'a' }, 8 | }; 9 | expect(graph.lookup(data)).toBeUndefined(); 10 | expect(graph.lookupOrInsertNode(data).data).toEqual(data); 11 | expect(graph.lookup(data)?.data).toEqual(data); 12 | expect(graph.isEmpty()).toBeFalsy(); 13 | graph.removeNode(data); 14 | expect(graph.lookup(data)).toBeUndefined(); 15 | expect(graph.isEmpty()).toBeTruthy(); 16 | }); 17 | 18 | test('insertEdge/root/remove', () => { 19 | const graph = new Graph((data: any) => data.id.toString()); 20 | const dataA = { 21 | id: { toString: () => 'a' }, 22 | }; 23 | const dataB = { 24 | id: { toString: () => 'b' }, 25 | }; 26 | const dataC = { 27 | id: { toString: () => 'c' }, 28 | }; 29 | const dataD = { 30 | id: { toString: () => 'd' }, 31 | }; 32 | 33 | graph.insertEdge(dataA, dataB); 34 | graph.insertEdge(dataA, dataC); 35 | graph.insertEdge(dataC, dataD); 36 | 37 | expect(graph.lookup(dataA)?.outcoming['b'].data).toEqual(dataB); 38 | expect(graph.lookup(dataA)?.outcoming['c'].data).toEqual(dataC); 39 | expect(graph.lookup(dataB)?.incoming['a'].data).toEqual(dataA); 40 | expect(graph.lookup(dataC)?.incoming['a'].data).toEqual(dataA); 41 | 42 | const rootNodes = graph.root(); 43 | expect(rootNodes.length).toBe(2); 44 | const rootDatas = rootNodes.map((rootNode) => rootNode.data); 45 | expect(rootDatas).toContain(dataB); 46 | expect(rootDatas).toContain(dataD); 47 | 48 | graph.removeNode(dataC); 49 | expect(graph.lookup(dataA)?.outcoming['c']).toBeUndefined(); 50 | expect(graph.lookup(dataD)?.incoming['c']).toBeUndefined(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dependency-injection", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "test": "run-s test:jest", 9 | "test:jest": "jest", 10 | "lint": "eslint --ext .ts .", 11 | "lint:fix": "eslint --ext .ts --fix .", 12 | "commitlint": "commitlint --color" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@commitlint/cli": "^6.2.0", 20 | "@commitlint/config-conventional": "^6.1.3", 21 | "@tencent/eslint-config-halo": "^7.0.0", 22 | "@types/jest": "^25.2.1", 23 | "@typescript-eslint/eslint-plugin": "^4.28.5", 24 | "@typescript-eslint/parser": "^4.28.5", 25 | "eslint": "^6.8.0", 26 | "husky": "^4.2.5", 27 | "jest": "^25.5.0", 28 | "lint-staged": "^10.2.0", 29 | "npm-run-all": "^4.1.2", 30 | "prettier": "^1.17.0", 31 | "prettier-check": "^2.0.0", 32 | "ts-jest": "^25.4.0", 33 | "tslint": "^5.16.0", 34 | "tslint-config-prettier": "^1.18.0", 35 | "typescript": "^3.4.5", 36 | "typescript-tslint-plugin": "^0.3.1" 37 | }, 38 | "husky": { 39 | "hooks": { 40 | "commit-msg": "commitlint -e .git/COMMIT_EDITMSG", 41 | "pre-commit": "lint-staged" 42 | } 43 | }, 44 | "lint-staged": { 45 | "*.ts": [ 46 | "eslint --fix", 47 | "git add" 48 | ] 49 | }, 50 | "jest": { 51 | "collectCoverage": true, 52 | "transform": { 53 | "^.+\\.ts$": "ts-jest" 54 | }, 55 | "moduleFileExtensions": [ 56 | "js", 57 | "ts" 58 | ], 59 | "testEnvironment": "node", 60 | "testMatch": [ 61 | "**/*.test.ts" 62 | ], 63 | "globals": { 64 | "ts-jest": { 65 | "tsConfig": "./tsconfig.json", 66 | "diagnostics": false 67 | } 68 | } 69 | }, 70 | "dependencies": { 71 | "babel-loader": "^8.1.0", 72 | "ts-loader": "^8.0.7" 73 | } 74 | } -------------------------------------------------------------------------------- /src/instantiation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { Ctor, ServiceIdentifier } from './typings/type'; 7 | 8 | // 用于获取target的$di$dependencies属性存储的依赖关系 9 | export namespace _util { 10 | export const serviceIds = new Map>(); 11 | export const DI_TARGET = '$di$target'; 12 | export const DI_DEPENDENCIES = '$di$dependencies'; 13 | 14 | // 获取 $di$dependencies 依赖 15 | export function getServiceDependencies(ctor: Ctor): { 16 | id: ServiceIdentifier; 17 | index: number; 18 | optional: boolean; 19 | }[] { 20 | return (ctor as any)[DI_DEPENDENCIES] || []; 21 | } 22 | } 23 | 24 | // 用于在target的$di$dependencies属性存储依赖关系 25 | export function storeServiceDependency( 26 | id: Function, 27 | target: Function, 28 | index: number, 29 | optional: boolean, 30 | ): void { 31 | if ((target as any)[_util.DI_TARGET] === target) { 32 | (target as any)[_util.DI_DEPENDENCIES].push({ id, index, optional }); 33 | } else { 34 | (target as any)[_util.DI_TARGET] = target; 35 | (target as any)[_util.DI_DEPENDENCIES] = [{ id, index, optional }]; 36 | } 37 | } 38 | 39 | // 创建decorator 40 | export function createDecorator(serviceId: string): ServiceIdentifier { 41 | if (_util.serviceIds.has(serviceId)) { 42 | return _util.serviceIds.get(serviceId) as ServiceIdentifier; 43 | } 44 | 45 | const id = function serviceIdentifier( 46 | target: Ctor, 47 | key: string, 48 | index: number, 49 | ): void { 50 | if (arguments.length !== 3) { 51 | throw new Error('@IServiceName-decorator can only be used to decorate a parameter'); 52 | } 53 | 54 | storeServiceDependency(id, target, index, false); 55 | }; 56 | 57 | id.toString = () => serviceId; 58 | _util.serviceIds.set(serviceId, id); 59 | return id; 60 | } 61 | 62 | export interface ServiceAccessor { 63 | get(id: ServiceIdentifier): T; 64 | } 65 | 66 | export interface IInstantiationService { 67 | createInstance any>( 68 | t: T, 69 | ...args: any[] 70 | ): InstanceType; 71 | } 72 | -------------------------------------------------------------------------------- /src/graph.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { ServiceIdentifier } from './typings/type'; 7 | import { SyncDescriptor } from './descriptors'; 8 | 9 | /* eslint-disable no-useless-constructor */ 10 | export interface GraphItem { 11 | id: ServiceIdentifier; 12 | desc: SyncDescriptor; 13 | } 14 | 15 | export interface Node { 16 | data: GraphItem; 17 | incoming: { 18 | [key: string]: Node; 19 | }; 20 | outcoming: { 21 | [key: string]: Node; 22 | }; 23 | } 24 | 25 | function newNode(data: T): Node { 26 | return { 27 | data, 28 | incoming: Object.create(null), 29 | outcoming: Object.create(null), 30 | }; 31 | } 32 | 33 | export class Graph { 34 | private readonly nodes: { [key: string]: Node } = Object.create(null); 35 | 36 | constructor(private readonly keyFn: (data: T) => string) { } 37 | 38 | /* 获取图结构的叶子节点 */ 39 | root(): Node[] { 40 | const ret: Node[] = []; 41 | for (const nodeKey in this.nodes) { 42 | if (Object.prototype.hasOwnProperty.call(this.nodes, nodeKey)) { 43 | const nodeElement: Node = this.nodes[nodeKey]; 44 | if (Object.getOwnPropertyNames(nodeElement.outcoming).length === 0) { 45 | ret.push(nodeElement); 46 | } 47 | } 48 | } 49 | 50 | return ret; 51 | } 52 | 53 | /* 从图结构中获取节点,如果节点不存在就创建一个并插入图中 */ 54 | lookupOrInsertNode(data: T): Node { 55 | const key = this.keyFn(data); 56 | let node = this.nodes[key]; 57 | if (!node) { 58 | node = newNode(data); 59 | this.nodes[key] = node; 60 | } 61 | 62 | return node; 63 | } 64 | 65 | /* 在图结构中插入一条边 */ 66 | insertEdge(from: T, to: T): void { 67 | const fromNode = this.lookupOrInsertNode(from); 68 | const toNode = this.lookupOrInsertNode(to); 69 | const fromKey = this.keyFn(from); 70 | const toKey = this.keyFn(to); 71 | fromNode.outcoming[toKey] = toNode; 72 | toNode.incoming[fromKey] = fromNode; 73 | } 74 | 75 | /* 在图结构中移除一个节点 */ 76 | removeNode(data: T): void { 77 | const delKey = this.keyFn(data); 78 | delete this.nodes[delKey]; 79 | for (const nodeKey in this.nodes) { 80 | if (Object.prototype.hasOwnProperty.call(this.nodes, nodeKey)) { 81 | const nodeElement: Node = this.nodes[nodeKey]; 82 | delete nodeElement.outcoming[delKey]; 83 | delete nodeElement.incoming[delKey]; 84 | } 85 | } 86 | } 87 | 88 | /* 在图结构中查询节点 */ 89 | lookup(data: T): Node | undefined { 90 | return this.nodes[this.keyFn(data)]; 91 | } 92 | 93 | /* 判断图结构是否为空 */ 94 | isEmpty(): boolean { 95 | return Object.keys(this.nodes).length === 0; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /test/intergration/instantiationService.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable no-useless-constructor */ 3 | import { createDecorator, _util } from '../../src/instantiation'; 4 | import { InstantiationService } from '../../src/instantiationService'; 5 | import { ServiceCollection } from '../../src/serviceCollection'; 6 | import { SyncDescriptor } from '../../src/descriptors'; 7 | 8 | describe('instantiationService', () => { 9 | test('createInstance with static arguments and static service instance', () => { 10 | class A { 11 | echo() { 12 | return 'A'; 13 | } 14 | } 15 | 16 | class B { 17 | echo() { 18 | return 'B'; 19 | } 20 | } 21 | 22 | const serviceA = createDecorator('A'); 23 | const serviceB = createDecorator('B'); 24 | class C { 25 | private leftText: string; 26 | private rightText: string; 27 | constructor(@serviceA private a: A, @serviceB private b: B, leftText = '', rightText = '') { 28 | this.leftText = leftText; 29 | this.rightText = rightText; 30 | } 31 | 32 | echo() { 33 | return `${this.a.echo()}${this.b.echo()}${this.leftText}${this.rightText}`; 34 | } 35 | } 36 | 37 | const aInstance = new A(); 38 | const bInstance = new B(); 39 | const svrsCollection = new ServiceCollection(); 40 | svrsCollection.set(serviceA, aInstance); 41 | svrsCollection.set(serviceB, bInstance); 42 | const instantiationService = new InstantiationService(svrsCollection); 43 | const cInstance = instantiationService.createInstance(C, 'L', 'R') as C; 44 | expect(cInstance instanceof C).toBeTruthy(); 45 | const echoText = cInstance.echo(); 46 | expect(echoText).toBe('ABLR'); 47 | }); 48 | 49 | test('createInstance with static arguments and dynamic service', () => { 50 | class A { 51 | echo() { 52 | return 'A'; 53 | } 54 | } 55 | 56 | const serviceA = createDecorator('A'); 57 | 58 | class B { 59 | constructor(@serviceA private a: A) {} 60 | echo() { 61 | return `${this.a.echo()}B`; 62 | } 63 | } 64 | 65 | const serviceB = createDecorator('B'); 66 | 67 | class C { 68 | private leftText: string; 69 | private rightText: string; 70 | constructor(@serviceB public b: B, leftText = '', rightText = '') { 71 | this.leftText = leftText; 72 | this.rightText = rightText; 73 | } 74 | 75 | echo() { 76 | return `${this.b.echo()}${this.leftText}${this.rightText}`; 77 | } 78 | } 79 | 80 | const aInstance = new SyncDescriptor(A); 81 | const bInstance = new SyncDescriptor(B, [], true); 82 | const svrsCollection = new ServiceCollection(); 83 | svrsCollection.set(serviceA, aInstance); 84 | svrsCollection.set(serviceB, bInstance); 85 | const instantiationService = new InstantiationService(svrsCollection); 86 | const cInstance = instantiationService.createInstance(C, 'L', 'R') as C; 87 | expect(cInstance instanceof C).toBeTruthy(); 88 | // 验证实例b是延迟初始化的 89 | expect(Object.getOwnPropertyNames(cInstance.b).length).toBe(0); 90 | const echoText = cInstance.echo(); 91 | expect(Object.getOwnPropertyNames(cInstance.b).length).toBe(1); 92 | expect(echoText).toBe('ABLR'); 93 | }); 94 | 95 | test('get service by invokeFunction', () => { 96 | class A { 97 | echo() { 98 | return 'A'; 99 | } 100 | } 101 | 102 | const serviceA = createDecorator('A'); 103 | 104 | class B { 105 | echo() { 106 | return 'B'; 107 | } 108 | } 109 | 110 | const serviceB = createDecorator('B'); 111 | 112 | 113 | const aInstance = new A(); 114 | const bInstance = new B(); 115 | const svrsCollection = new ServiceCollection(); 116 | svrsCollection.set(serviceA, aInstance); 117 | svrsCollection.set(serviceB, bInstance); 118 | const instantiationService = new InstantiationService(svrsCollection); 119 | 120 | instantiationService.invokeFunction((accessor) => { 121 | expect(accessor.get(serviceA)).toBe(aInstance); 122 | expect(accessor.get(serviceB)).toBe(bInstance); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/instantiationService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 1998 - 2021 Tencent. All Rights Reserved. 3 | * @author enoyao 4 | */ 5 | 6 | import { Ctor, ServiceIdentifier, ServicesAccessor } from './typings/type'; 7 | import { Graph, GraphItem } from './graph'; 8 | import { 9 | IInstantiationService, 10 | _util, 11 | } from './instantiation'; 12 | import { ServiceCollection } from './serviceCollection'; 13 | import { SyncDescriptor } from './descriptors'; 14 | 15 | export class IdleValue { 16 | private readonly executor: () => void; 17 | private didRun = false; 18 | private value: T | undefined; 19 | private error: any; 20 | constructor(executor: () => T) { 21 | this.executor = () => { 22 | try { 23 | this.value = executor(); 24 | } catch (error) { 25 | this.error = error; 26 | } finally { 27 | this.didRun = true; 28 | } 29 | }; 30 | } 31 | 32 | getValue(): T { 33 | if (!this.didRun) { 34 | this.executor(); 35 | } 36 | 37 | if (this.error) { 38 | throw this.error; 39 | } 40 | 41 | return this.value as T; 42 | } 43 | } 44 | 45 | 46 | export class InstantiationService implements IInstantiationService { 47 | private readonly services: ServiceCollection; 48 | 49 | constructor(serices: ServiceCollection = new ServiceCollection()) { 50 | this.services = serices; 51 | } 52 | 53 | createInstance( 54 | ctorOrDescriptor: new (...args: any[]) => T | SyncDescriptor, 55 | ...rest: any[] 56 | ): any { 57 | const result = this.createCtorInstance(ctorOrDescriptor, rest); 58 | return result; 59 | } 60 | 61 | 62 | invokeFunction( 63 | fn: (accessor: ServicesAccessor, ...args: TS) => R, 64 | ...args: TS 65 | ): R { 66 | let done = false; 67 | try { 68 | const accessor: ServicesAccessor = { 69 | get: (id: ServiceIdentifier) => { 70 | if (done) { 71 | throw new Error('service accessor is only valid during the invocation of its target method'); 72 | } 73 | 74 | const result = this.getOrCreateServiceInstance(id); 75 | if (!result) { 76 | throw new Error(`[invokeFunction] unknown service '${id}'`); 77 | } 78 | 79 | return result; 80 | }, 81 | }; 82 | return fn(accessor, ...args); 83 | } finally { 84 | done = true; 85 | } 86 | } 87 | 88 | 89 | private createCtorInstance( 90 | ctor: new (...args: any[]) => T, 91 | args: any[] = [], 92 | ): T { 93 | const serviceDependencies = _util 94 | .getServiceDependencies(ctor) 95 | .sort((a, b) => a.index - b.index); 96 | const serviceArgs: any[] = []; 97 | for (const dependency of serviceDependencies) { 98 | const serviceInstance = this.getOrCreateServiceInstance(dependency.id); 99 | serviceArgs.push(serviceInstance); 100 | } 101 | 102 | // eslint-disable-next-line new-cap 103 | return new ctor(...[...serviceArgs, ...args]); 104 | } 105 | 106 | private getOrCreateServiceInstance(id: ServiceIdentifier) { 107 | const thing = this.getServiceInstanceOrDescriptor(id); 108 | if (thing instanceof SyncDescriptor) { 109 | return this.createAndCacheServiceInstance(id, thing); 110 | } 111 | 112 | return thing; 113 | } 114 | 115 | private getServiceInstanceOrDescriptor(id: ServiceIdentifier): T | SyncDescriptor { 116 | const instanceOrDesc = this.services.get(id); 117 | if (!instanceOrDesc) { 118 | throw new Error(`service ${id.toString()} is not in collection`); 119 | } 120 | 121 | return instanceOrDesc; 122 | } 123 | 124 | private createAndCacheServiceInstance(id: ServiceIdentifier, desc: SyncDescriptor): T { 125 | const graph = new Graph((data) => data.id.toString()); 126 | const stack: GraphItem[] = [{ id, desc }]; 127 | while (stack.length) { 128 | const item = stack.pop() as GraphItem; 129 | graph.lookupOrInsertNode(item as any); 130 | for (const dependency of _util.getServiceDependencies(item.desc.ctor)) { 131 | const instanceOrDesc = this.getServiceInstanceOrDescriptor(dependency.id); 132 | if (instanceOrDesc instanceof SyncDescriptor) { 133 | const dItem = { id: dependency.id, desc: instanceOrDesc }; 134 | graph.insertEdge(item, dItem); 135 | stack.push(dItem); 136 | } 137 | } 138 | } 139 | 140 | /* eslint-disable-next-line no-constant-condition */ 141 | while (true) { 142 | const roots = graph.root(); 143 | if (roots.length === 0) { 144 | break; 145 | } 146 | 147 | for (const { data } of roots) { 148 | const service = this.createServiceInstance( 149 | data.id, 150 | data.desc.ctor, 151 | data.desc.staticArguments, 152 | data.desc.supportsDelayedInstantiation, 153 | ); 154 | this.services.set(data.id, service); 155 | graph.removeNode(data); 156 | } 157 | } 158 | 159 | const service = this.getOrCreateServiceInstance(id); 160 | return service; 161 | } 162 | 163 | private createServiceInstance( 164 | id: ServiceIdentifier, 165 | ctor: Ctor, args: any[], 166 | supportsDelayedInstantiation: boolean, 167 | ): T { 168 | if (!(this.services.get(id) instanceof SyncDescriptor)) { 169 | throw Error(`illegalState - create UNKNOW service instance ${id.toString()}`); 170 | } 171 | 172 | if (!supportsDelayedInstantiation) { 173 | return this.createCtorInstance(ctor, args); 174 | } 175 | 176 | const idleObj = new IdleValue(() => this.createCtorInstance(ctor, args)); 177 | return new Proxy(Object.create(null), { 178 | get(target: any, key: PropertyKey): any { 179 | if (key in target) { 180 | return target[key]; 181 | } 182 | 183 | const obj = idleObj.getValue(); 184 | const prop = obj[key]; 185 | if (typeof prop === 'function') { 186 | const bindProp = prop.bind(obj); 187 | target[key] = bindProp; 188 | return bindProp; 189 | } 190 | 191 | target[key] = prop; 192 | return prop; 193 | }, 194 | set(target: T, key: PropertyKey, value: any): boolean { 195 | const obj = idleObj.getValue(); 196 | obj[key] = value; 197 | return true; 198 | }, 199 | }); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IOC/DI 2 | 3 | When it comes to dependency injection, you must first understand IOC and DI. 4 | 5 | - IOC, the full name of Inversion Of Control, inversion of control is a design idea of ​​object-oriented programming, mainly used to reduce the degree of coupling between codes. 6 | 7 | - DI, full name Dependency Injection, dependency injection is the concrete realization of IOC. It refers to the instantiation process of the object through external injection to avoid the object's internal realization of external dependencies. 8 | 9 | The IOC control inversion design pattern can greatly reduce the coupling of the program. In VSCode's inversion of control design pattern, the decorator's main function is to realize the DI dependency injection function and streamline part of the repetitive writing. Since the implementation of this step is more complicated, let's start with a simple example as a starting point to understand the basic principles of decorators. 10 | 11 | # Implementation 12 | 13 | `@serviceA` and `@serviceB` are parameter decorators, which are used to process parameters and are created by the `createDecorator` method. 14 | 15 | - @Parameter decorator usage: receive three parameters 16 | - target: For static members, it is the constructor of the class, and for instance members, it is the prototype chain of the class 17 | - key: the name of the method, note that it is the name of the method, not the name of the parameter 18 | - index: the index of the position of the parameter in the method 19 | - @Return: The returned value will be ignored 20 | 21 | ```ts 22 | class C { 23 | constructor(@serviceA private a: A, @serviceB private b: B) {} 24 | } 25 | ``` 26 | 27 | All parameter decorators are created by the createDecorator method, and `'A'` and `'B'` are the unique identifiers of the decorator. 28 | 29 | ```ts 30 | const serviceA = createDecorator("A"); 31 | const serviceB = createDecorator("B"); 32 | ``` 33 | 34 | The decorator first judges whether it is cached, if it is cached, the cached parameter decorator is taken out, if it is not cached, a parameter decorator of `serviceIdentifier` is created. 35 | 36 | ```ts 37 | function createDecorator(serviceId: string): ServiceIdentifier { 38 | if (_util.serviceIds.has(serviceId)) { 39 | return _util.serviceIds.get(serviceId) as ServiceIdentifier; 40 | } 41 | } 42 | ``` 43 | 44 | The only thing that the `serviceIdentifier` parameter decorator does is to trigger the `storeServiceDependency` to save all dependencies, store the decorator itself `id`, the parameter index `index` and whether it is optional `optional`. 45 | 46 | ```ts 47 | const id = function serviceIdentifier(target: Ctor, key: string, index: number): void { 48 | storeServiceDependency(id, target, index, false); 49 | }; 50 | id.toString = () => serviceId; 51 | _util.serviceIds.set(serviceId, id); 52 | ``` 53 | 54 | The essence of `storeServiceDependency` is to set two static attributes `$di$target` and `$di$dependencies` on `target`, that is, `class C`. The `target` is stored on them respectively, and the `target` itself has to be stored again. It is to judge whether the dependency has been saved. 55 | 56 | ```ts 57 | C.$di$target; // class C 58 | C.$di$dependencies[0].id.toString(); // A or B 59 | C.$di$dependencies; // [{id: serviceIdentifier, index: 1, optional: false}, {id: serviceIdentifier, index: 0, optional: false}] 60 | ``` 61 | 62 | In addition to existing classes, there are also `_util.serviceIds`. 63 | 64 | When the class is declared, the decorator is applied, so the dependencies are determined before the class is instantiated. Compiling `ts` can prove this point. You can see that the decorator will be executed when `__decorate` is declared in the class. 65 | 66 | ```ts 67 | var C = /** @class */ (function() { 68 | function C(a, b) { 69 | this.a = a; 70 | this.b = b; 71 | } 72 | C = __decorate([__param(0, serviceA), __param(1, serviceB)], C); 73 | return C; 74 | })(); 75 | ``` 76 | 77 | Then comes `ServiceCollection`, where the decorator will be used as the key to uniquely identify, and the instantiated class will be stored as the value in `svrsCollection`. The implementation of `svrsCollection` is also very simple. Use the `Map` method to store it directly. . 78 | 79 | ```ts 80 | const aInstance = new A(); 81 | const bInstance = new B(); 82 | const svrsCollection = new ServiceCollection(); 83 | svrsCollection.set(serviceA, aInstance); 84 | svrsCollection.set(serviceB, bInstance); 85 | ``` 86 | 87 | Afterwards, you only need to use the get method and pass in the corresponding parameter decorator to get the corresponding instantiated class. 88 | 89 | ```ts 90 | svrsCollection.get(serviceA); // new A() 91 | svrsCollection.get(serviceB); // new B() 92 | ``` 93 | 94 | `InstantiationService` is the core of the implementation of dependency injection. It uses parameter decorators, such as `serviceA` and `serviceB`, etc.`ServiceIdentifier` as the `key`. All the dependency injections that are instantiated are stored in the private variable `services` kind. `services` saves `svrsCollection`. 95 | 96 | ```ts 97 | const instantiationService = new InstantiationService(svrsCollection); 98 | ``` 99 | 100 | It exposes three public methods: 101 | 102 | -`createInstance` This method accepts a class and the non-dependency injection parameters for constructing the class, and then creates an instance of the class. -`invokeFunction` This method accepts a callback function, which can access all dependency injection items in the `InstantiationService` through the `acessor` parameter. -`createChild` This method accepts a set of dependencies and creates a new `InstantiationService` to show that the dependency injection mechanism of vscode is also hierarchical. 103 | 104 | The `createInstance` method is the core method of instantiation: 105 | 106 | ```ts 107 | const cInstance = instantiationService.createInstance(C, "L", "R") as C; 108 | ``` 109 | 110 | The first is to get the `ctorOrDescriptor`, which is the class `class C`, and the parameter `rest` that needs to be passed in for non-dependency injection. 111 | 112 | ```ts 113 | const result = this.createCtorInstance(ctorOrDescriptor, rest); 114 | ``` 115 | 116 | Then use `getServiceDependencies` to obtain and sort the `$di$dependencies` that mounts the static properties of `class C`, because the order is reversed when stored 117 | 118 | ```ts 119 | const serviceDependencies = _util 120 | .getServiceDependencies(ctor) 121 | .sort((a, b) => a.index - b.index); 122 | ``` 123 | 124 | The retrieved dependency `serviceDependencies` is mainly to traverse and obtain the parameter decorators `serviceA` and `serviceB` inside. 125 | 126 | ```ts 127 | const serviceArgs: any[] = []; 128 | for (const dependency of serviceDependencies) { 129 | const serviceInstance = this.getOrCreateServiceInstance(dependency.id); 130 | serviceArgs.push(serviceInstance); 131 | } 132 | ``` 133 | 134 | The essence of `getOrCreateServiceInstance` is to get the instantiated class from `services` that is `svrsCollection`. 135 | 136 | ```ts 137 | const instanceOrDesc = this.services.get(id); 138 | // Equivalent to id that is the parameter decorator 139 | // svrsCollection.get(id); 140 | ``` 141 | 142 | When all these instantiated classes are taken out and put into `serviceArgs`, since the parameter decorator is executed when the class is instantiated and the dependencies are collected, `serviceArgs` corresponds to `ctor` ie`Class C` needs to be injected with dependent parameters. Combining non-dependent parameters can help us successfully instantiate our `ctor` class. 143 | 144 | ```ts 145 | new ctor(...[...serviceArgs, ...args]); 146 | ``` 147 | -------------------------------------------------------------------------------- /README.CN.md: -------------------------------------------------------------------------------- 1 | # IOC/DI 2 | 3 | 谈到依赖注入,必须先理解 IOC 与 DI。 4 | 5 | - IOC,全称 Inversion Of Control,控制反转是面向对象编程的一种设计思想,主要用来降低代码之间的耦合度。 6 | 7 | - DI,全称 Dependency Injection,依赖注入是 IOC 的具体实现。是指对象通过外部的注入,避免对象内部自身实现外部依赖的实例化过程。 8 | 9 | IOC 控制反转的设计模式可以大幅度地降低了程序的耦合性。而 装饰器在 VSCode 的控制反转设计模式里,其主要作用是实现 DI 依赖注入的功能和精简部分重复的写法。由于该步骤实现较为复杂,我们先从简单的例子为切入点去了解装饰器的基本原理。 10 | 11 | # Implementation 12 | 13 | `@serviceA` 和 `@serviceB` 是参数装饰器,用于处理参数,是由 `createDecorator` 方法创建的。 14 | 15 | - @参数装饰器使用方法:接收三个参数 16 | - target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链 17 | - key: 方法的名称,注意是方法的名称,而不是参数的名称 18 | - index: 参数在方法中所处的位置的下标 19 | - @返回:返回的值将会被忽略 20 | 21 | ```ts 22 | class C { 23 | constructor(@serviceA private a: A, @serviceB private b: B) {} 24 | } 25 | ``` 26 | 27 | 所有参数装饰器均由 createDecorator 方法创建,`'A'` 和 `'B'`,均是该装饰器的唯一标识。 28 | 29 | ```ts 30 | const serviceA = createDecorator("A"); 31 | const serviceB = createDecorator("B"); 32 | ``` 33 | 34 | 装饰器首先判断是否被缓存,如果有被缓存则取出已经缓存好的参数装饰器,如果没被缓存,则创建一个 `serviceIdentifier` 的参数装饰器。 35 | 36 | ```ts 37 | function createDecorator(serviceId: string): ServiceIdentifier { 38 | if (_util.serviceIds.has(serviceId)) { 39 | return _util.serviceIds.get(serviceId) as ServiceIdentifier; 40 | } 41 | } 42 | ``` 43 | 44 | `serviceIdentifier` 参数装饰器只做了一件事就是触发 `storeServiceDependency` 把所有依赖项给存起来,存装饰器本身 `id`,参数的下标 `index` 以及是否可选 `optional`。 45 | 46 | ```ts 47 | const id = function serviceIdentifier(target: Ctor, key: string, index: number): void { 48 | storeServiceDependency(id, target, index, false); 49 | }; 50 | id.toString = () => serviceId; 51 | _util.serviceIds.set(serviceId, id); 52 | ``` 53 | 54 | `storeServiceDependency` 本质是往 `target` 即 `class C` 上设置两个静态属性 `$di$target` 和 `$di$dependencies` 上面分别存 `target`,自身还要再存一次自身 `target` 是为了判断是否已经存过依赖。 55 | 56 | ```ts 57 | C.$di$target; // class C 58 | C.$di$dependencies[0].id.toString(); // A 或者 B 59 | C.$di$dependencies; // [{id: serviceIdentifier, index: 1, optional: false}, {id: serviceIdentifier, index: 0, optional: false}] 60 | ``` 61 | 62 | 除了存在类上,还存在了 `_util.serviceIds` 上。 63 | 64 | 当类声明的时候,装饰器就会被应用,所以在有类被实例化之前依赖关系就已经确定好了。把 `ts` 编译就可以证明这点,可以看到 `__decorate` 在类声明的时候,装饰器就会被执行了, 65 | 66 | ```ts 67 | var C = /** @class */ (function() { 68 | function C(a, b) { 69 | this.a = a; 70 | this.b = b; 71 | } 72 | C = __decorate([__param(0, serviceA), __param(1, serviceB)], C); 73 | return C; 74 | })(); 75 | ``` 76 | 77 | 紧接着就到了 `ServiceCollection`,这里会将装饰器作为 key 唯一标识,实例化的类作为 value,全部存到 `svrsCollection` 中,`svrsCollection` 的实现也很简单,直接用 `Map` 方法存起来。 78 | 79 | ```ts 80 | const aInstance = new A(); 81 | const bInstance = new B(); 82 | const svrsCollection = new ServiceCollection(); 83 | svrsCollection.set(serviceA, aInstance); 84 | svrsCollection.set(serviceB, bInstance); 85 | ``` 86 | 87 | 后续只需要使用 get 方法并传入对应的参数装饰器就可以获取对应的实例化好的类了。 88 | 89 | ```ts 90 | svrsCollection.get(serviceA); // new A() 91 | svrsCollection.get(serviceB); // new B() 92 | ``` 93 | 94 | `InstantiationService` 是实现依赖注入的核心,它是以参数装饰器,例如 `serviceA` 和 `serviceB` 等 `ServiceIdentifier` 为 `key` 在私有变量 `services` 中保存所有依赖注入的被实例化好的类。`services` 保存的是 `svrsCollection`。 95 | 96 | ```ts 97 | const instantiationService = new InstantiationService(svrsCollection); 98 | ``` 99 | 100 | 它暴露了三个公开方法: 101 | 102 | - `createInstance` 该方法接受一个类以及构造该类的非依赖注入参数,然后创建该类的实例。 103 | - `invokeFunction` 该方法接受一个回调函数,该回调函数通过 `acessor` 参数可以访问该 `InstantiationService` 中的所有依赖注入项。 104 | - `createChild` 该方法接受一个依赖项集合,并创造一个新的 `InstantiationService` 说明 vscode 的依赖注入机制也是有层次的。 105 | 106 | `createInstance` 方法是实例化的核心方法: 107 | 108 | ```ts 109 | const cInstance = instantiationService.createInstance(C, "L", "R") as C; 110 | ``` 111 | 112 | 首先是获取 `ctorOrDescriptor` 也就是类 `class C` 和需要传入非依赖注入的参数 `rest`。 113 | 114 | ```ts 115 | const result = this.createCtorInstance(ctorOrDescriptor, rest); 116 | ``` 117 | 118 | 然后使用 `getServiceDependencies` 把挂载 `class C` 静态属性的 `$di$dependencies` 给获取出来并排序,因为存的时候顺序是倒序的 119 | 120 | ```ts 121 | const serviceDependencies = _util 122 | .getServiceDependencies(ctor) 123 | .sort((a, b) => a.index - b.index); 124 | ``` 125 | 126 | 取出来的依赖项 `serviceDependencies` 主要是为了遍历并获取里面的参数装饰器 `serviceA` 和 `serviceB`。 127 | 128 | ```ts 129 | const serviceArgs: any[] = []; 130 | for (const dependency of serviceDependencies) { 131 | const serviceInstance = this.getOrCreateServiceInstance(dependency.id); 132 | serviceArgs.push(serviceInstance); 133 | } 134 | ``` 135 | 136 | `getOrCreateServiceInstance` 本质就是从 `services` 即 `svrsCollection` 中获取实例化好的类。 137 | 138 | ```ts 139 | const instanceOrDesc = this.services.get(id); 140 | // 相当于 id 即参数装饰器 141 | // svrsCollection.get(id); 142 | ``` 143 | 144 | 当把所有的这些实例化好的类取出来放到 `serviceArgs` 中后,由于参数装饰器是类实例化的时候就执行完并收集好依赖项,所以 `serviceArgs` 就是对应 `ctor` 即 `class C` 需要注入的依赖参数,合并非依赖参数就能帮助我们成功实例化好我们的 `ctor` 类。 145 | 146 | ```ts 147 | new ctor(...[...serviceArgs, ...args]); 148 | ``` 149 | 150 | 151 | # InversifyJS 152 | 153 | # 通过 @injectable 装饰器标记类可注入 154 | 155 | - `injectable` 是可注入的意思,也就是告知依赖注入框架这个类需要被注册到容器中 156 | - `inject` 是注入的意思,它是一个装饰器工厂 157 | 158 | 用 `injectable` 装饰器来标志一个类是否是可依赖注入的,用 `inject` 装饰器来标志要注入的依赖类型,在解析阶段的时候会根据 `inject` 的参数在容器中查找依赖并注入。 159 | 160 | ```ts 161 | import { Container, injectable, ContainerModule } from "inversify"; 162 | export interface IProvider { 163 | getName(): T; 164 | } 165 | 166 | @injectable() 167 | export class NameProvider implements IProvider { 168 | getName() { 169 | return "World"; 170 | } 171 | } 172 | 173 | // Container 可以看作是整个依赖注入链路的入口 174 | const container = new Container(); 175 | 176 | // 一般依赖注入容器都是这种格式 ioc.bind(key, value) 177 | // InversifyJS 只是把它换成同等的形式 ioc.bind(key).to(value) 178 | container.bind>("nameProvider").to(NameProvider); 179 | // 1. 通过 nameProvider 字符串绑定,则使用 nameProvider 字符串来获取 180 | // const nameProvider: any = container.get("nameProvider"); 181 | 182 | container.bind>(NameProvider).toSelf().inSingletonScope(); 183 | // 2.通过 NameProvider 类绑定,则需要 NameProvider 类来获取 184 | // const nameProvider: any = container.get(NameProvider); 185 | ``` 186 | 187 | # 通过 ContainerModule 分层管理 188 | 189 | 在像 `theia` 这样的大型项目中,如果我们全部的依赖都直接绑定在 `Container` 上,显然不那么美观。而 `ContainerModule` 则是用于管理众多绑定的方法。 190 | 通过 `ContainerModule`,我们就可以把绑定分散到不同的模块中,可以使架构条理更清晰。 191 | 192 | ```ts 193 | export interface IconTheme { 194 | canHandle(): number; 195 | getIcon(): string; 196 | } 197 | 198 | @injectable() 199 | export class NoneIconTheme implements IconTheme { 200 | readonly id = "none"; 201 | readonly label = "None"; 202 | readonly description = "Disable file icons"; 203 | readonly hasFileIcons = true; 204 | readonly hasFolderIcons = true; 205 | 206 | canHandle(): number { 207 | return Number.MAX_SAFE_INTEGER; 208 | } 209 | 210 | getIcon(): string { 211 | return ""; 212 | } 213 | } 214 | 215 | const frontendApplicationModule = new ContainerModule( 216 | (bind, unbind, isBound, rebind) => { 217 | // to:绑定一个类(获取类的实例) 218 | // toSelf:to 的简化版,当 serviceIdentifier(标识符)是构造函数时,直接绑定自身。 219 | // toConstantValue:绑定一个常量 220 | // toDynamicValue:绑定为动态数值,解析时执行传入的函数获取依赖。 221 | // 后面可继续链式处理in、when、on 222 | bind(NoneIconTheme).toSelf().inSingletonScope(); 223 | } 224 | ); 225 | 226 | container.load(frontendApplicationModule); 227 | ``` 228 | 229 | # 通过 get 获取注入 230 | 231 | ```ts 232 | const nameProvider: any = container.get("nameProvider"); 233 | const nameProvider: any = container.get(NameProvider); 234 | 235 | const iconTheme: any = container.get(NoneIconTheme); 236 | console.log(nameProvider.getName()); 237 | ``` 238 | 239 | # InversifyJS 工作流 240 | 241 | InversifyJS 的工作流程主要分为五个阶段: 242 | 243 | | 阶段 | 描述 | 执行代码 | 244 | | ---------- | ---------- | ----------------------------------- | 245 | | Annotation | 打标签阶段 | `@injectable` 和 `@inject` 标记阶段 | 246 | | Planning | 规划阶段 | container.bind 阶段 | 247 | | Middleware | 中间件阶段 | container.get 阶段 | 248 | | Resolution | 解析阶段 | | 249 | | Activation | 激活阶段 | 进入缓存触发,已在缓存不触发 | 250 | --------------------------------------------------------------------------------