InversifyJS 是一个轻量的 (4KB) 控制反转容器 (IoC),可用于编写 TypeScript 和 JavaScript 应用。 它使用类构造函数去定义和注入它的依赖。

因为 ES6 之后 JavaScript 支持用 Class 方式用于OOP编程,但要做好 OOP 设计是很困难的,InversifyJS 它能帮助 JavaScript 开发者,写出出色的面向对象设计的代码。

InversifyJS 的设计哲学:

  • 允许 JavaScript 开发人员编写遵循 SOLID 原则的代码
  • 促进并鼓励遵守最佳的 OOP 编程和 IoC 实践
  • 尽可能少的运行时开销
  • 提供艺术编程体验和生态

安装

1
$ npm install inversify reflect-metadata --save

InversifyJS 依赖于 TypeScript 2.0。需要对tsconfig.jsoncompilerOptions进行experimentalDecorators,emitDecoratorMetadata,typelib配置:

1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"target": "es5",
"lib": ["es6", "dom"],
"types": ["reflect-metadata"],
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

inversifyjs 需要现代 JavaScript 引擎,支持以下特性

如果您的  运行环境不支持  这些  特性,您可能需要导入 shimpolyfill

:警示: reflect-metadata polyfill 应该在您整个应用中只导入一次 因为 Reflect 对象  需要成为一个全局的单例。 更多细节可以在这里找到。

查看维基中的开发环境 polyfills , 还可以从基本示例中去学习.

一个简单例子 🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// entites.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces";
import { TYPES } from "./types";

@injectable()
class Katana implements Weapon {
public hit() {
return "cut!";
}
}

@injectable()
class Shuriken implements ThrowableWeapon {
public throw() {
return "hit!";
}
}

@injectable()
class Ninja implements Warrior {
private _katana: Weapon;
private _shuriken: ThrowableWeapon;

public constructor(
@inject(TYPES.Weapon) katana: Weapon,
@inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
) {
this._katana = katana;
this._shuriken = shuriken;
}
fight(): string {
return this._katana.hit();
}
sneak(): string {
return this._shuriken.throw();
}
}

export { Ninja, Katana, Shuriken };

// interface.ts
export interface Warrior {
fight(): string;
sneak(): string;
}

export interface Weapon {
hit(): string;
}

export interface ThrowableWeapon {
throw(): string;
}

// types.ts
const TYPES = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon"),
ThrowableWeapon: Symbol.for("ThrowableWeapon"),
};

export { TYPES };

// inversify.config.ts
import { Container } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
myContainer.bind<Weapon>(TYPES.Weapon).to(Katana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);

export { myContainer };

// index.ts
import "reflect-metadata";
import { Warrior } from "./interfaces";
import { myContainer } from "./inversify.config";
import { TYPES } from "./types";

const ninja = myContainer.get<Warrior>(TYPES.Warrior);

console.log(ninja.fight());
  • injectable - 声明 class 可以用来注入
  • inject - 用来声明来需要注入的 对象

inversify.config.ts 说明 声明和对象之间的关系。这个文件是 inversifyJS 用来管理声明和对象之间关系的。

myContainer.get<Warrior>(TYPES.Warrior) 获取需要使用的对象

核心能力

容器模块

当应用项目很复杂的时候,借助容器模块可以帮助我们管理应用中复杂的绑定关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// entities.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces";
import { TYPES } from "./types";

@injectable()
class Katana implements Weapon {
public hit() {
return "cut!";
}
}

@injectable()
class Shuriken implements ThrowableWeapon {
public throw() {
return "hit!";
}
}

@injectable()
class Ninja implements Warrior {
private _katana: Weapon;
private _shuriken: ThrowableWeapon;

public constructor(
@inject(TYPES.Weapon) katana: Weapon,
@inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
) {
this._katana = katana;
this._shuriken = shuriken;
}
fight(): string {
return this._katana.hit();
}
sneak(): string {
return this._shuriken.throw();
}
}

export { Ninja, Katana, Shuriken };

// interfaces.ts
export interface Warrior {
fight(): string;
sneak(): string;
}

export interface Weapon {
hit(): string;
}

export interface ThrowableWeapon {
throw(): string;
}

// inversify.config.ts
import { Container, ContainerModule, interfaces } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

// 容器可以用来绑定 class直接的关系
let warriorModule = new ContainerModule(
(bind: interfaces.Bind, unbind: interfaces.Unbind) => {
bind<Warrior>(TYPES.Warrior).to(Ninja);
}
);

let weaponModule = new ContainerModule(
(bind: interfaces.Bind, unbind: interfaces.Unbind) => {
bind<Weapon>(TYPES.Weapon).to(Katana);
}
);

let throwableWeaponModule = new ContainerModule(
(bind: interfaces.Bind, unbind: interfaces.Unbind) => {
bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);
}
);

let container = new Container();

container.load(warriorModule, weaponModule, throwableWeaponModule);

export { warriorModule, weaponModule, throwableWeaponModule };

// types.ts
const TYPES = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon"),
ThrowableWeapon: Symbol.for("ThrowableWeapon"),
};

export { TYPES };

// index.ts
import { Container } from "inversify";
import "reflect-metadata";
import { Warrior } from "./interfaces";
import {
warriorModule,
weaponModule,
throwableWeaponModule,
} from "./inversify.config";
import { TYPES } from "./types";

let container = new Container();

container.load(warriorModule, weaponModule, throwableWeaponModule);

const ninja = container.get<Warrior>(TYPES.Warrior);

console.log(ninja.fight());

注入工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// entities.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces";
import { TYPES } from "./types";

@injectable()
class Katana implements Weapon {
public hit() {
return "cut!";
}
}

@injectable()
class Shuriken implements ThrowableWeapon {
public throw() {
return "hit!";
}
}

@injectable()
class Ninja implements Warrior {
private _katana: Weapon;
private _shuriken: ThrowableWeapon;

public constructor(
// 绑定一个工厂函数
@inject(TYPES.Weapon) katana: (n: any) => Weapon,
@inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
) {
this._katana = katana(1); // 可以向工厂函数传递参数
this._shuriken = shuriken;
}
fight(): string {
return this._katana.hit();
}
sneak(): string {
return this._shuriken.throw();
}
}

export { Ninja, Katana, Shuriken };

// interfaces.ts
export interface Warrior {
fight(): string;
sneak(): string;
}

export interface Weapon {
hit(): string;
}

export interface ThrowableWeapon {
throw(): string;
}

// types.ts
const TYPES = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon"),
ThrowableWeapon: Symbol.for("ThrowableWeapon"),
};

export { TYPES };

// inversify.config.ts
import { Container, interfaces } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
myContainer.bind<Weapon>("TYPES.Weapon").to(Katana);
// 绑定工厂函数
myContainer
.bind<interfaces.Factory<Weapon>>(TYPES.Weapon)
.toFactory<Weapon>((context: interfaces.Context) => {
return (s) => {
// 接收参数
console.log(s);
return context.container.get<Weapon>("TYPES.Weapon");
};
});
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);

export { myContainer };

// index.ts
import "reflect-metadata";
import { Warrior } from "./interfaces";
import { myContainer } from "./inversify.config";
import { TYPES } from "./types";

const ninja = myContainer.get<Warrior>(TYPES.Warrior);

console.log(ninja.fight());
// 1
// cut!

激活句柄

给一个类型添加一个激活句柄是可能的。激活句柄在依赖被找到后、但是还没有被添加到缓存(如果是单例的话)并注入之前被调用。 这非常有用,因为这样可以让我们的依赖不必关注横切面(比如缓存、日志等)的具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// entities.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces";
import { TYPES } from "./types";

@injectable()
class Katana implements Weapon {
public hit() {
return "cut!";
}
}

@injectable()
class Shuriken implements ThrowableWeapon {
public throw() {
return "hit!";
}
}

@injectable()
class Ninja implements Warrior {
private _katana: Weapon;
private _shuriken: ThrowableWeapon;

public constructor(
@inject(TYPES.Weapon) katana: Weapon,
@inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
) {
this._katana = katana;
this._shuriken = shuriken;
}
fight(): string {
return this._katana.hit();
}
sneak(): string {
return this._shuriken.throw();
}
}

export { Ninja, Katana, Shuriken };

// interfaces.ts
export interface Warrior {
fight(): string;
sneak(): string;
}

export interface Weapon {
hit(): string;
}

export interface ThrowableWeapon {
throw(): string;
}

// inversify.config.ts
import { Container, interfaces } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
// 给一个类型添加一个激活句柄是可能的。激活句柄在依赖被找到后、但是还没有被添加到缓存(如果是单例的话)并注入之前被调用。
// 感觉不如直接 写个 AOP装饰器来的方便
myContainer
.bind<Weapon>(TYPES.Weapon)
.to(Katana)
.onActivation((context: interfaces.Context, _katana) => {
let handle = {
apply(target: any, thisArgument: any, argumentsList: any) {
console.log(`Starting: ${new Date().getTime()}`);
let result = target.apply(thisArgument, argumentsList);
console.log(`Finished:${new Date().getTime()}`);
return result;
},
};
_katana.hit = new Proxy(_katana.hit, handle);
return _katana;
});
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);

export { myContainer };

// types.ts
const TYPES = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon"),
ThrowableWeapon: Symbol.for("ThrowableWeapon"),
};

export { TYPES };

// index.ts
import "reflect-metadata";
import { Warrior } from "./interfaces";
import { myContainer } from "./inversify.config";
import { TYPES } from "./types";

const ninja = myContainer.get<Warrior>(TYPES.Warrior);

console.log(ninja.fight());

自己写个 TS 装饰器 感觉比用激活句柄更方便。

构造器后置装饰器

可以添加一个 @postContruct 装饰器到一个类或者方法。这个装饰器将在一个对象被示例化之后,以及在所有激活句柄之前运行。 当构造器已经被调用但是组件还没有初始化或者你想在构造器被调用后执行一个初始化逻辑时非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// entities.ts
import { injectable, postConstruct } from "inversify";
import { IKatana } from "./types";

@injectable()
class Katana implements IKatana {
constructor() {
console.log("Katana is born!");
}

public use() {
return "Used Katana!";
}

/**
* @postConstruct 装饰器到一个类或者方法。
* 这个装饰器将在一个对象被示例化之后,
* 以及在所有激活句柄之前运行。
* 当构造器已经被调用但是组件还没有初始化或者你想在构造器被调用后执行一个初始化逻辑时非常有用。
*/
@postConstruct()
public testMethod() {
console.log("testMethod");
}
}

export { Katana };

// inversify.config.ts
import { Container } from "inversify";
import { Katana } from "./entities";
import { IKatana } from "./types";

const MyContainer = new Container();
MyContainer.bind<IKatana>("Katana").to(Katana);

export { MyContainer };

// types.ts
export interface IKatana {
use: () => void;
}

// index.ts
import "reflect-metadata";
import { Warrior } from "./interfaces";
import { myContainer } from "./inversify.config";
import { TYPES } from "./types";

const ninja = myContainer.get<Warrior>(TYPES.Warrior);

console.log(ninja.fight());

名称绑定

当有一个抽象被绑定到多个具体实现时,我们可以通过设置@name来区分同一个抽象的不同具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface Weapon {}

@injectable()
class Katana implements Weapon {}

@injectable()
class Shuriken implements Weapon {}

interface Ninja {
katana: Weapon;
shuriken: Weapon;
}

@injectable()
class Ninja implements Ninja {
public katana: Weapon;
public shuriken: Weapon;
public constructor(
@inject("Weapon") @named("strong") katana: Weapon,
@inject("Weapon") @named("weak") shuriken: Weapon
) {
this.katana = katana;
this.shuriken = shuriken;
}
}

container.bind<Ninja>("Ninja").to(Ninja);
container.bind<Weapon>("Weapon").to(Katana).whenTargetNamed("strong");
container.bind<Weapon>("Weapon").to(Shuriken).whenTargetNamed("weak");

InversifyJS 还提供了标签绑定上下文绑定默认绑定用来处理一个抽象会有多个具体实现的情况。

用于绑定的 InversifyJS 流利语法包括一些已经实现的通用上下文约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface BindingWhenSyntax<T> {
when(
constraint: (request: interfaces.Request) => boolean
): interfaces.BindingOnSyntax<T>;
whenTargetNamed(name: string): interfaces.BindingOnSyntax<T>;
whenTargetTagged(tag: string, value: any): interfaces.BindingOnSyntax<T>;
whenInjectedInto(parent: Function | string): interfaces.BindingOnSyntax<T>;
whenParentNamed(name: string): interfaces.BindingOnSyntax<T>;
whenParentTagged(tag: string, value: any): interfaces.BindingOnSyntax<T>;
whenAnyAncestorIs(ancestor: Function | string): interfaces.BindingOnSyntax<T>;
whenNoAncestorIs(ancestor: Function | string): interfaces.BindingOnSyntax<T>;
whenAnyAncestorNamed(name: string): interfaces.BindingOnSyntax<T>;
whenAnyAncestorTagged(tag: string, value: any): interfaces.BindingOnSyntax<T>;
whenNoAncestorNamed(name: string): interfaces.BindingOnSyntax<T>;
whenNoAncestorTagged(tag: string, value: any): interfaces.BindingOnSyntax<T>;
whenAnyAncestorMatches(
constraint: (request: interfaces.Request) => boolean
): interfaces.BindingOnSyntax<T>;
whenNoAncestorMatches(
constraint: (request: interfaces.Request) => boolean
): interfaces.BindingOnSyntax<T>;
}

中间件

InversifyJS 在解决依赖前执行 3 个强制操作:

  • 标记
  • 计划
  • 解决

某些情况下会有额外操作:

  • 激活
  • 中间件

如果我们配置了中间件,那么它会在计划阶段、解决阶段以及激活阶段之前或者之后的某个时间点执行。

中间件可被用来实现强大的开发工具。这些工具可以帮助开发者在开发过程中定位问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// entities.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces";
import { TYPES } from "./types";

@injectable()
class Katana implements Weapon {
public hit() {
return "cut!";
}
}

@injectable()
class Shuriken implements ThrowableWeapon {
public throw() {
return "hit!";
}
}

@injectable()
class Ninja implements Warrior {
private _katana: Weapon;
private _shuriken: ThrowableWeapon;

public constructor(
@inject(TYPES.Weapon) katana: Weapon,
@inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
) {
this._katana = katana;
this._shuriken = shuriken;
}
fight(): string {
return this._katana.hit();
}
sneak(): string {
return this._shuriken.throw();
}
}

export { Ninja, Katana, Shuriken };

// interface.ts
export interface Warrior {
fight(): string;
sneak(): string;
}

export interface Weapon {
hit(): string;
}

export interface ThrowableWeapon {
throw(): string;
}

// inversify.config.ts
import { Container, interfaces } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

function logger(planAndResolve: interfaces.Next): interfaces.Next {
return (args: interfaces.NextArgs) => {
let start = new Date().getTime();
let result = planAndResolve(args);
let end = new Date().getTime();
console.log(`Woooo ${end - start}`);
return result;
};
}

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
myContainer.bind<Weapon>(TYPES.Weapon).to(Katana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);
myContainer.applyMiddleware(logger);

export { myContainer };

// types.ts
const TYPES = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon"),
ThrowableWeapon: Symbol.for("ThrowableWeapon"),
};

export { TYPES };

// index.ts
import "reflect-metadata";
import { Warrior } from "./interfaces";
import { myContainer } from "./inversify.config";
import { TYPES } from "./types";

const ninja = myContainer.get<Warrior>(TYPES.Warrior);

console.log(ninja.fight());

层次化的依赖倒置系统

在层次化依赖倒置系统中,容器可以有父容器,并且一个应用中可以有多个容器。这些容器形成了层次化的结构。

当一个处于层次结构中最底层的容器请求一个依赖项时,容器尝试在其自身内部的绑定中查找。如果缺少相关绑定,它向上传递请求到父容器。若父容器查找不到相关绑定则再次向上传递请求。请求被向上冒泡传递直到有个容器能够处理这个请求为止或者贯穿整个祖先容器链。

1
2
3
4
5
6
7
8
9
10
11
12
let weaponIdentifier = "Weapon";

@injectable()
class Katana {}

let parentContainer = new Container();
parentContainer.bind(weaponIdentifier).to(Katana);

let childContainer = new Container();
childContainer.parent = parentContainer;

expect(childContainer.get(weaponIdentifier)).to.be.instanceOf(Katana); // true

问题

循环依赖

在类的互相依赖中,是很可能出现循环依赖的情况的。

错误: 找到了循环依赖:Ninja -> A -> B -> C -> D -> A

InversifyJS 是可以检测到这样的情况的,如果不做处理会提示错误:

1
@inject 和 undefined 一起被调用了,这可能意味着类 ${name} 存在一个循环依赖问题。你可以用 LazyServiceIdentifer 来克服这个限制。

你有两种方式解决这个问题:

  • 使用 LazyServiceIdentifer。 懒加载识别器并不延迟依赖注入,所有依赖项都在类实例创建时注入。但是,它延迟了对属性识别器的访问(解决了模块问题)。一个相关的例子可从官方提供的单元测试中找到。
  • 使用 @lazyInject 装饰器。该装饰器是 inversify-inject-decorators 模块的一部分。 @lazyInject 装饰器将对依赖项的注入延迟到了真正要使用它们的那一刻,这发生在类实例被创建之后。

继承

InvsersifyJS 中对继承的使用有两条限制条件:

  • 一个子类必须显式地声明其构造器。
  • 子类的构造器参数数量必须大于或等于其基类的构造器参数数量。

如果你不准守这两个规则条件则会 收获 异常警告:

1
Error: The number of constructor arguments in the derived class SamuraiMaster must be >= than the number of constructor arguments of its base class.

有四种方法来解决可能出现的继承报错问题

使用 @unmanaged 装饰器

@unmanaged() 装饰器运行用户标记一个参数将会被手动注入到基类。我们使用 “unmanaged” 一词因为 InversifyJS 对用户提供的值没有控制权,就不能管理它们的注入行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const BaseId = "Base";

@injectable()
class Base {
public prop: string;
public constructor(@unmanaged() arg: string) {
this.prop = arg;
}
}

@injectable()
class Derived extends Base {
public constructor() {
super("unmanaged-injected-value");
}
}

container.bind<Base>(BaseId).to(Derived);
let derived = container.get<Base>(BaseId);

derived instanceof Derived2; // true
derived.prop; // "unmanaged-injected-value"

属性设置器

你可以使用 publicprotected 或者 private 访问权限标记和一个属性设置器来避免注入到基类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@injectable()
class Warrior {
protected rank: string;
public constructor() {
// args count = 0
this.rank = null;
}
}

@injectable()
class SamuraiMaster extends Warrior {
public constructor() {
// args count = 0
super();
this.rank = "master";
}
}

属性注入

我们也可以使用属性注入来避免注入到基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@injectable()
class Warrior {
protected rank: string;
public constructor() {} // args count = 0
}

let TYPES = { Rank: "Rank" };

@injectable()
class SamuraiMaster extends Warrior {
@injectNamed(TYPES.Rank, "master")
@named("master")
protected rank: string;

public constructor() {
// args count = 0
super();
}
}

container
.bind<string>(TYPES.Rank)
.toConstantValue("master")
.whenTargetNamed("master");

注入到子类

如果我们不想避免注入到基类我们可以注入到子类然后使用基类的构造器(super)注入到基类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@injectable()
class Warrior {
protected rank: string;
public constructor(rank: string) {
// args count = 1
this.rank = rank;
}
}

let TYPES = { Rank: "Rank" };

@injectable()
class SamuraiMaster extends Warrior {
public constructor(
@inject(TYPES.Rank) @named("master") rank: string // args count = 1
) {
super(rank);
}
}

container
.bind<string>(TYPES.Rank)
.toConstantValue("master")
.whenTargetNamed("master");

下面的代码也可行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@injectable()
class Warrior {
protected rank: string;
public constructor(rank: string) {
// args count = 1
this.rank = rank;
}
}

interface Weapon {
name: string;
}

@injectable()
class Katana implements Weapon {
public name: string;
public constructor() {
this.name = "Katana";
}
}

let TYPES = {
Rank: "Rank",
Weapon: "Weapon",
};

@injectable()
class SamuraiMaster extends Warrior {
public weapon: Weapon;
public constructor(
@inject(TYPES.Rank) @named("master") rank: string, // args count = 2
@inject(TYPES.Weapon) weapon: Weapon
) {
super(rank);
this.weapon = weapon;
}
}

container.bind<Weapon>(TYPES.Weapon).to(Katana);

container
.bind<string>(TYPES.Rank)
.toConstantValue("master")
.whenTargetNamed("master");

第三方基类使用

某些情况下,你也许会碰到错误说一个第三方模块提供的类缺少标记,比如:

1
Error: Missing required @injectable annotation in: SamuraiMaster

你可以通过使用 decorate 函数来克服这个问题:

1
2
3
4
5
import { decorate, injectable } from "inversify";
import SomeClass from "some-module";

decorate(injectable(), SomeClass);
return SomeClass;

最佳实践

InversifyJS 是一个 IoC 容器,一个 IoC 容器是一个工具,用来帮助你写出随时间推移容易修改和扩展的面向对象的代码。

组合重用原则

‘对象组合’优于‘类继承’。

继承是一件坏事情,因为它带来了模块间的最强耦合性。

依赖倒置原则

要依赖抽象,而不要依赖具体实现。

依赖注入无非是通过构造器或者设置器将依赖传递给类

1
2
3
4
5
6
7
8
9
10
11
12
@injectable()
class Ninja {
private _katana: Katana;

public constructor(katana: Katana) {
this._katana = katana;
}

public fight() {
return this._katana.hit();
}
}

这个例子中 Ninja 类依赖 Katana 类: Ninja --> Katana

注意箭头方向表示了依赖是从左到右的。

如果我们更新 Ninja 类来依赖 Katana 类的抽象(即 Katana 接口):

1
2
3
4
5
6
7
8
9
10
11
12
@injectable()
class Ninja {
private _katana: Katana;

public constructor(@inject("Katana") katana: Katana) {
this._katana = katana;
}

public fight() {
return this._katana.hit();
}
}

在这种情况下,Ninja 类和 Katana 类都依赖于 Katana 接口:

Ninja --> Katana

Ninja --> Katana

也可以表示为:

Ninja --> Katana <-- Katana

避免注入数据,而不是行为

注入数据,而不是行为,是政治家反模式的一个子类型,只是在这里容器是那个没用的类。如果一个类需要知道当前日期和时间,那么没有必要注入一个日期和时间,即数据;相反的,你应该注入一个系统时钟的上层抽象。这不仅仅在 DI 时是正确的做法;这对可测性至关重要,因为你可以不必等待就能够测试不同的时间下的行为。

避免将所有的生命周期声明为单例

将所有的生命周期声明为单例,对我来说,这就是货物崇拜编程的完美例子,简直是俗称的“对象污水池”。我看过多到我记不住的单例滥用案例,其中很少和 DI 相关。

避免构造器的过度注入

构造器的过度注入违反了单一职责原则。太多的构造器参数表明了太多的依赖;太多的依赖表明了这个类尝试做得太多了。通常这个错误伴随着其他的代码坏味道,比如通常有着很长而且模糊的类名称(xxxManager 之类)。

Refer To

https://github.com/inversify/InversifyJS/tree/master/wiki

https://doc.inversify.cloud/zh_cn/

https://zhuanlan.zhihu.com/p/137542149