Skip to content

Commit

Permalink
refactor: 抽取 container 中拓扑排序部分到 utils/graph
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Nov 19, 2020
1 parent 486bddb commit 560f12e
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 130 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ['jest-extended'],
testMatch: [
'<rootDir>/test/**/?(*.)(spec|test).ts?(x)'
]
Expand Down
46 changes: 46 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"deploy": "gulp deploy",
"docs": "typedoc --out docs src && touch docs/.nojekyll",
"build": "gulp build",
"prepublish": "npm run build",
"coverage": "jest --coverage",
"coveralls": "jest --coverage && cat coverage/lcov.info | npx coveralls",
"watch": "gulp watch",
Expand Down Expand Up @@ -67,6 +66,7 @@
"gulp-transform-cache": "^1.1.1",
"gulp-typescript": "^5.0.1",
"jest": "^24.8.0",
"jest-extended": "^0.11.5",
"merge2": "^1.2.3",
"reflect-metadata": "^0.1.13",
"semantic-release": "^17.1.1",
Expand Down
69 changes: 12 additions & 57 deletions src/di/container.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { isProviderClass, Service, ProviderClass, ServiceClass, Factory } from './provider';
import { Provider, isProviderClass, Service, ProviderClass, ServiceClass, Factory } from './provider';
import { createValueProvider } from './value-provider-impl';
import { createServiceProvider } from './service-provider-impl';
import { createFactoryProvider } from './factory-provider-impl';
import { Map } from '../utils/map';
import { InjectToken } from './inject-token';
import { getDependencies, setDependencies } from './dependency';
import { Graph } from '../utils/graph';

/**
* 依赖注入容器
*
* 根据控制反转的概念: [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control) (IoC),Container 作为控制反转的容器,当你给容器提供一个 token 时,容器会自动的根据这个 token 值去注入对应的依赖,而这需要 `@inject` 和 `@injectable` 去生成 metadata。
*/
export class Container {
private providers: Map
private providerClasses: Map
private providers: Map<InjectToken, Provider<any>>
private providerClasses: Map<InjectToken, ProviderClass<any>>
private services: Service[] = [];
private prerequisites: any[][] = [];
private graph = new Graph<InjectToken>();
public childContainers: Container[] = [];
public parent?: Container;

Expand All @@ -40,9 +41,10 @@ export class Container {
if (!this.parent) throw new Error(`provider for ${fn} not found`);
return this.parent.getOrCreateProvider(fn);
}
this.graph.addVertex(fn);
const deps = ProviderClass.dependencies().map(dep => {
// 先决数组中不包括父容器的provider
this.providerClasses.get(dep) && this.prerequisites.push([fn, dep]);
if (this.providerClasses.has(dep)) this.graph.addEdge(fn, dep);
return this.create(dep, fn);
});
provider = new ProviderClass(...deps);
Expand Down Expand Up @@ -96,71 +98,24 @@ export class Container {
}

public getTokens () {
const tokens: InjectToken[] = [];
this.providerClasses.keys(token => {
tokens.push(token);
});
return tokens;
return this.providerClasses.keys();
}

public getServices () {
return this.services.slice();
}

public getSortedList () {
const inDegree = new Map();
const graph = new Map();
this.providers.keys((key) => inDegree.set(key, 0));
// 生成入度map和哈希表
for (let i = 0; i < this.prerequisites.length; i++) {
const degreeVal: number = inDegree.get(this.prerequisites[i][0]);
inDegree.set(this.prerequisites[i][0], degreeVal + 1);
if (graph.get(this.prerequisites[i][1])) {
const nowGraph = graph.get(this.prerequisites[i][1]);
nowGraph.push(this.prerequisites[i][0]);
graph.set(this.prerequisites[i][1], nowGraph);
} else {
graph.set(this.prerequisites[i][1], [this.prerequisites[i][0]]);
}
}
const result = [];
const queue = [];
inDegree.keys((key) => {
if (inDegree.get(key) === 0) {
queue.push(key);
}
});
while (queue.length) {
const cur = queue.shift();
result.push(cur);
const toEnQueue = graph.get(cur);
if (toEnQueue && toEnQueue.length) {
for (let i = 0; i < toEnQueue.length; i++) {
const inDegreeVal = inDegree.get(toEnQueue[i]);
if (inDegreeVal === 1) {
queue.push(toEnQueue[i]);
} else {
inDegree.set(toEnQueue[i], inDegreeVal - 1);
}
}
}
}
return result;
}

/**
* 销毁容器,以及容器里的所有 service,并调用所有 service 的 `destroy()` 方法(如果存在定义的话)。inject-js 会去分析已创建的所有 Service 实例,按照依赖的拓扑顺序,逆序小伙。如:A 依赖 B,B 依赖 C,则按照 A => B => C 的顺序依次调用它们的 destroy 方法。
*/
public destroy () {
for (const child of this.childContainers) {
child.destroy();
}
const providers = this.getSortedList();
for (let index = providers.length - 1; index >= 0; index--) {
const element = providers[index];
const thisProvider = this.providers.get(element);
if (typeof thisProvider.destroy === 'function') {
thisProvider.destroy();
for (const token of this.graph.popAll()) {
const provider = this.providers.get(token);
if (typeof provider.destroy === 'function') {
provider.destroy();
}
}
this.providers.clear();
Expand Down
1 change: 0 additions & 1 deletion src/di/inject-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class Token<T> {
}

export type InjectToken<T = any> = Token<T> | ServiceClass<T>;
// export type InjectToken<T> = Token<T> | typeof Service;

export function createInjectToken<T = any> (): InjectToken<T> {
return new Token<T>();
Expand Down
4 changes: 4 additions & 0 deletions src/utils/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function includes<T> (arr: T[], val: T): boolean {
for (const item of arr) if (item === val) return true;
return false;
}
37 changes: 37 additions & 0 deletions src/utils/graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Map, decrease, increase } from './map';

export class Graph<T> {
private inDegrees = new Map<T, number>()
private adj = new Map<T, T[]>()
// 添加边
addEdge (fr: T, to: T) {
const edges = this.adj.get(fr) || [];
edges.push(to);
this.adj.set(fr, edges);
increase(this.inDegrees, to);
}
addVertex (v: T) {
if (!this.adj.has(v)) {
this.adj.set(v, []);
}
}
// 按照拓扑序移除所有节点
popAll () {
const free = [];
const ordered = [];
for (const key of this.adj.keys()) {
if (!this.inDegrees.get(key)) free.push(key);
}
while (free.length) {
const fr = free.shift();
ordered.push(fr);
for (const to of this.adj.get(fr) || []) {
const degree = decrease(this.inDegrees, to);
if (!degree) free.push(to);
}
}
this.inDegrees.clear();
this.adj.clear();
return ordered;
}
}
8 changes: 0 additions & 8 deletions src/utils/includes.ts

This file was deleted.

81 changes: 51 additions & 30 deletions src/utils/map.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,55 @@
import { includes } from './array';

/**
* ES6 Map 的部分实现
*/
export class Map {
private _keys: any[] = []
private _values: any[] = []

set (key: any, value: any) {
for (let i = 0; i < this._keys.length; i++) {
if (this._keys[i] === key) {
this._values[i] = value;
return;
}
}
this._keys.push(key);
this._values.push(value);
}

get (key) {
for (let i = 0; i < this._keys.length; i++) {
if (this._keys[i] === key) return this._values[i];
}
return null;
}

clear () {
this._keys = [];
this._values = [];
}

keys (callbackFn) {
return this._keys.forEach(callbackFn);
}
export class Map<KeyType, ValueType> {
private _keys: KeyType[] = []
private _values: ValueType[] = []

set (key: KeyType, value: ValueType) {
for (let i = 0; i < this._keys.length; i++) {
if (this._keys[i] === key) {
this._values[i] = value;
return;
}
}
this._keys.push(key);
this._values.push(value);
}

get (key: KeyType): ValueType | undefined {
for (let i = 0; i < this._keys.length; i++) {
if (this._keys[i] === key) return this._values[i];
}
}

has (key: KeyType): boolean {
return includes(this._keys, key);
}

clear () {
this._keys = [];
this._values = [];
}

keys () {
return this._keys;
}

values () {
return this._values;
}
}

export function increase<T> (map: Map<T, any>, key: T) {
const degree = (map.get(key) || 0) + 1;
map.set(key, degree);
return degree;
}

export function decrease<T> (map: Map<T, any>, key: T) {
const degree = (map.get(key) || 0) - 1;
map.set(key, degree);
return degree;
}

0 comments on commit 560f12e

Please sign in to comment.