InversifyJS介绍

前文讲到,Theia使用InversifyJS实现依赖注入。本文介绍一下InversifyJS。

InversifyJS 官网 https://inversify.io/,github地址:https://github.com/inversify/InversifyJS,是一个轻量级的,typescript编写的,支持JavaScript & Node.js的依赖注入工具。

具体怎么用,看了下面的例子就明白了。

 整体项目的文件列表如下:

 

 

 所有文件我打了下包,放在这里:https://files.cnblogs.com/files/blogs/759971/inversify.zip,有需要可以下载研究,代码我粘贴如下:

interfaces.ts

/**
 * 这个文件定义了3个名词,以及这3个名词可以干什么动词。
 * 一般接口的定义模式就是名词里面套动词
 */

// 这是个战士
export interface Warrior {
    fight(): string; //这个战士可以战斗
    sneak(): string; //他也可以偷袭
}

// 这是个武器
export interface Weapon {
    hit(): string; //可以打击
}

//这是个能扔出去的武器,比如类似标枪、石头之类的东西 
export interface ThrowableWeapon {
    throw(): string; //
}
entities.ts
// file entities.ts

import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces";
import { TYPES } from "./types";

// 日本武士刀,实现了Weapon这个接口。
// 接口一般是一个抽象名词,类会比接口具像一些。比如可以把 人类 定义成一个接口,然后 学生、老师 可以定义成实现了人类这个接口的类,学生和老师相比人类,会有更具像、明显的特征。
// @injectable 表示这个类可以被绑定到container里
@injectable()
class Katana implements Weapon {
    public hit() {
        return "cut!";
    }
}

// 手里剑,实现了ThrowableWeapon这个接口。
// 手里剑是日本对脱手暗器的统称,脱手暗器就是出手后不再收回再次使用的暗器。手里剑分为棒状手里剑和车剑。棒状手里剑有单尖的,俗称飞针,双尖的,名为千本。
// 车剑就是俗话说的忍者镖,中国叫流星镖,有3-8个尖,盘式飞行。棒状手里剑携带轻便,制作简单,但使用起来难度高,需要长久的练习达到一定的技术才可以使用,技术的高低直接影响着射程的远近和准度。 对于棒状手里剑的国内练习者在网络上成立了一个暗器联盟,主要是推广棒状手里剑的使用技术。
@injectable()
class Shuriken implements ThrowableWeapon {
    public throw() {
        return "hit!";
    }
}

// 这个忍者实现了战士的接口
@injectable()
class Ninja implements Warrior {
    private _katana: Weapon;
    private _shuriken: ThrowableWeapon;
    private calledCount : number = 0;

    // 忍者 初始化需要两个参数,类型是 Weapon 和 ThrowableWeapon
    // 这里inject的用途表示这两个参数,不是用户创建Ninja实例的时候传的参数。而是在myContainer.bind的时候从myContainer自己内部map上按TYPES的key取到的
    public constructor(
        @inject(TYPES.WeaponKey) katana: Weapon,
        @inject(TYPES.ThrowableWeaponKey) shuriken: ThrowableWeapon
    ) {
        this._katana = katana;
        this._shuriken = shuriken;
        this.calledCount = this.calledCount+1; //加这个是为了演示一下每次myContainer.get都是一个新实例,和单例模式不一样

        console.log("Ninja inited, " + this.calledCount); 
    }

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

}

// 这个钢铁侠实现了战士的接口
@injectable()
class IronMan implements Warrior {

    private _katana: Weapon;
    private _shuriken: ThrowableWeapon;

    // 忍者 初始化需要两个参数,类型是 Weapon 和 ThrowableWeapon
    public constructor(
        @inject(TYPES.WeaponKey) katana: Weapon,
        @inject(TYPES.ThrowableWeaponKey) shuriken: ThrowableWeapon
    ) {
        this._katana = katana;
        this._shuriken = shuriken;
        console.log("IronMan inited"); // cut!
    }

    public fight() { return this._katana.hit(); }
    public sneak() { return "I am Esquire, I wouldn't do such a thing! !"; }

}

export { Ninja, Katana, Shuriken, IronMan};

 

 
types.ts
// file types.ts

// 关于Symbol的用法, 讲的不错的一个文章:https://zhuanlan.zhihu.com/p/297923315
// 这里就是为了给一个标识,相当于定义了四个map的key一样
const TYPES = {
    NinjaKey: Symbol.for("Ninja"),
    IronManKey: Symbol.for("IronMan"),
    WeaponKey: Symbol.for("Weapon"),
    ThrowableWeaponKey: Symbol.for("ThrowableWeapon")
};

export { TYPES };

inversify.config.ts

// file inversify.config.ts

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

// 这里相当于新建了一个map
const myContainer = new Container();

//这里相当于要创建一个Ninja类实例,并放在myContainer这个map里,用的key是TYPES里取的常量
myContainer.bind<Warrior>(TYPES.NinjaKey).to(Ninja); //忍者

myContainer.bind<Warrior>(TYPES.IronManKey).to(IronMan); //钢铁侠
myContainer.bind<Weapon>(TYPES.WeaponKey).to(Katana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeaponKey).to(Shuriken);

export { myContainer };

 

main.ts,执行入口

import { myContainer } from "./inversify.config";
import { TYPES } from "./types";
import { Warrior } from "./interfaces";

// 经过我仔细琢磨,myContainer可以理解为一个map. 
// myContainer.get表示从map里取值,key来自TYPES定义
const ninja = myContainer.get<Warrior>(TYPES.NinjaKey);

// 每次get都会新建一个实例,ninja1和上面的ninja是两个独立的实例,不是单例模式
// const ninja1 = myContainer.get<Warrior>(TYPES.NinjaKey);

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

console.log(ninja.fight()); // cut!
console.log(ninja.sneak()); // hit!

console.log(ironMan.fight()); // cut!
console.log(ironMan.sneak()); // I am Esquire, I wouldn't do such a thing! !

tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom","es6"],
        "types": ["reflect-metadata"],
        "module": "commonjs",
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

README.md

# ts文件运行方式 
1. 安装typescript: npm install -g typescript
2. 编译ts: tsc xxx.ts,会生成xxx.js文件
3. 运行: node xxx.js

# 运行本示例
2. 在本项目目录下执行: tsc --outDir build
3. 运行:node build/main.js

# 编译注意
如果使用 “tsc xxx.ts” 命令进行编译,那么是不会读取当前目录的配置文件tsconfig.json的,而是直接使用内置的配置,这时候的 target 默认是 es5,所以识别不了 es6 的 symbol;
而如果是使用 “tsc” 命令编译,不带文件名,则可以读取到配置文件;如果需要指定读取的配置文件的路径,可以使用 -p 路径参数。可以执行tsc -h 查看tsc指令的完整使用说明。

 

执行上面的示例工程,需要终端进入到项目根目录,执行:

tsc --outDir build
node build/main.js

运行结果:

Ninja inited, 1
IronMan inited
cut!
hit!
cut!
I am Esquire, I wouldn't do such a thing! !

 

总结

如果我们不用这种依赖注入的方式。常规方式就是:

1、创建两个武器实例

2、再创建Nijia的实例,传入上面两个参数。如下:

const katana = new Katana()
const shuriken =  new Shuriken()

const ninja =  new Ninja(katana, shuriken)
ninja.fight()

 

这二者的区别是什么呢?

如果不用DI依赖注入的方式,而用常规的这种方式,当我们调用第三方写的类的时候,需要知道非常多的细节,比如上面,调用Ninja还需要知道Katana和Shuriken类,而且如果有一些Ninja换武器了,不用Katana了,第三方修改了他的代码,我们也得跟着改。

如果用DI的方式,我们就不需要关注Katana和Shuriken类。第三方实现的是myContainer.bind<Warrior>(TYPES.NinjaKey).to(Ninja); 把他的实现放在container里就行了。我们用的时候只需要像main.ts里那样。get出来,然后调用就好了。根本不关注他fight的时候用是的什么武器,第三方有一天换武器了,我们也不需要改代码。

依赖注入怎么理解呢?就是Ninja依赖的类,Katana和Shuriken,是框架自动注入进去的。有的资料里也叫依赖反转,指依赖关系本来要我们调用方处理,变成包的提供方处理了。

posted @ 2022-07-18 18:35  theiaide  阅读(992)  评论(2编辑  收藏  举报