magic-di
v1.4.0-alpha.0
Published
Common dependency injector.
Downloads
7
Readme
common-di
基于 Angular DI 的设计自行实现的一个依赖注入的工具,https://angular.io/guide/dependency-injection。
如果你想在项目中使用工厂模式或者依赖反转,那么必不可少的需要一个依赖注入的工具。这个工具将会帮助你很好的帮助你实现依赖反转,而不在关系那些对象实例化的细节。同时,因为对象的实例化在注册器中进行创建,所以单例模式也很容易在这里实现。
Table of Contents
Background
出于对于 DI 的需要,并且调研市场上的 DI 工具,都有一些不太满意的地方,所以自己写了一个工具来实现 Angular 定义的 DI 模式。
- Inversify.Js 用法太过于死板,显示的 Token 模式,导致要写很多代码,相较于 API 还是 Angular 的 Token 和 Provider 的设计更容易理解。
- injection-js 从 Angular 抽离出来的 DI 工具,因为具有 Angular 的 Module 设计,一般的 DI 是不需要要的。
Token
查找实例的唯一标记,类型描述如下:
export type Token = string | symbol | Function;
Provider
为 Token 提供实例的定义,一共有四种类型的 Provider 定义:
export type Provider =
ClassProvider |
TypeProvider |
ValueProvider |
FactoryProvider;
ClassProvider
定义一个 Token 使用某个特定的构造函数的时候会用到的 Provider。
export interface ClassProvider {
token: Token;
useClass: ConstructorOf<any>;
}
在依赖反转之后,构造函数都依赖抽象而不依赖实例的时候会非常有效。比如下面的例子:
interface Driveable {
drive(): void;
}
@Injectable()
class Student {
@Autowired('Driveable')
mBike: Driveable;
goToSchool() {
console.log('go to school');
mBike.drive();
}
}
学生对象依赖的是一个可驾驶的交通工具,可以在创建对象的时候提供一个自行车,也可以在创建的时候提供一个汽车:
@Injectable()
class Car implements Driveable {
drive() {
console.log('by car')
}
}
injector.addProviders(Student)
injector.addProviders({
token: 'Driveable',
useClass: Car,
})
const student = injector.get(Student);
student.goToSchool(); // print 'go to school by car'
TypeProvider
token 和 useClass 都是一样的 ClassProvider,一般不会直接用到,使用 Autowired 如果是一个 Function,就会转换成这个类型。
ValueProvider
和 ClassProvider 一样作用,但是直接提供一个对象实例的 Provider。
export interface ValueProvider {
token: Token;
useValue: any;
}
FactoryProvider
和 ClassProvider 一样作用,但是提供一个函数进行对象实例创建的 Provider。
export interface FactoryProvider {
token: Token;
useFactory: () => any;
}
Install
$ npm install common-di --save
Usage
对 Construtor 进行构造注入
@Injectable()
class A {
constructor() {
console.log('Create A');
}
}
@Injectable()
class B {
constructor(public a: A){}
}
const injector = new Injector();
injector.addProviders(A, B);
const b = injector.get(B); // print 'Create A'
console.log(b.a instanceof A) // print 'true'
使用 Autowired 进行动态注入
@Injectable()
class A {
constructor() {
console.log('Create A');
}
}
@Injectable()
class B {
@Autowired()
a: A;
}
const injector = new Injector();
injector.addProviders(A, B);
const b = injector.get(B);
console.log(b.a instanceof A); // print 'Create A'; print 'true'
单例与多例的用法
@Injectable()
class Single {
constructor() {}
}
@Injectable({ mutiple: true })
class Mutiple {
constructor() {}
}
const injector = new Injector();
injector.addProviders(Single, Mutiple);
const single1 = injector.get(Single);
const single2 = injector.get(Single);
console.log(single1 === single2); // print 'true'
const mutiple1 = injector.get(Mutiple);
const mutiple2 = injector.get(Mutiple);
console.log(mutiple1 === mutiple2); // print 'false'
类型依赖抽象而不是依赖实现的用法
const LOGGER_TOKEN = Symbol('LOGGER_TOKEN');
interface Logger {
log(msg: string): void;
}
@Injectable()
class App {
@Autowired(LOGGER_TOKEN)
logger: Logger;
}
@Injectable()
class LoggerImpl implements Logger {
log(msg: string) {
console.log(msg);
}
}
const injector = new Injector();
injector.addProviders(App);
injector.addProviders({
token: LOGGER_TOKEN,
useClass: LoggerImpl,
});
const app = injector.get(App);
console.log(app.logger instanceof LoggerImpl); // print 'true'
使用抽象函数作为 Token 进行依赖注入
abstract class Logger {
abstract log(msg: string): void;
}
@Injectable()
class LoggerImpl implements Logger {
log(msg: string) {
console.log(msg);
}
}
@Injectable()
class App {
@Autowired()
logger: Logger;
}
const injector = new Injector();
injector.addProviders(App);
injector.addProviders({
token: Logger,
useClass: LoggerImpl,
});
const app = injector.get(App);
console.log(app.logger instanceof LoggerImpl); // print 'true'
API
@Injectable
interface InstanceOpts {
mutiple?: boolean;
}
function Injectable(opts?: InstanceOpts): ClassDecorator;
@Injectable({ mutiple: true })
class A {}
const injector = new Injector([A]);
const a = injector.get(A);
console.log(injector.hasInstance(a)) // print 'false'
所有需要被 Injector 创建的构造函数都应该使用这个装饰器修饰才可以正常使用,否则会报错
- mutiple: 是否启用多例模式,一旦启用了多例模式之后,Injector 将不会持有实例对象的引用。
@Autowired
function Autowired(token?: Token): PropertyDecorator;
@Injectable()
class A {}
@Injectable()
class B {
@Autowired()
a: A;
}
修饰一个属性会被注册器动态创建依赖实例,而这个依赖实例只有在被使用的时候才会被创建出来。比如上面的例子中,只有访问到 b.a
的时候,才会创建 A 的实例。
需要注意的是,因为 Autowired 依赖着 Injector 的实例,所以只有从 Injector 创建出来的对象可以使用这个装饰器
@Inject
function Inject(token: string | symbol): ParameterDecorator;
interface IA {
log(): void;
}
@Injectable()
class B {
constructor(@Inject('IA') a: IA) {}
}
在构造函数进行依赖注入的时候,需要特别指定依赖 Token 的时候的装饰器。当一个构造函数依赖某个抽象,并且这个抽象是在构造函数中传递进来的时候,会需要使用这个装饰器。
Injector.get
多态实现:
interface Injector<T extends Token> {
get(token: ConstructorOf<any>, args?: ConstructorParameters<T>, opts?: InstanceOpts): TokenResult<T>;
get(token: T, opts?: InstanceOpts): TokenResult<T>;
}
从 Injector 获取一个对象实例的方法,如果传递的是一个构造函数,第二个参数可以传递构造函数 Arguments 数据,此时将会直接将构造函数创建实例返回,并附加依赖注入的功能,此时的构造函数不需要被 Injectable 装饰也能正常创建对象。例如下面这样:
@Injectable()
class A {}
class B {
@Autowired()
a: A;
}
const injector = new Injector([A])
const b = injector.get(B, []);
console.log(b.a instanceof A); // print 'true'
Injector.hasInstance
Injector 中是否具备某个对象的单例引用
Example Readmes
更多的用例可以 点击查看.
Related Efforts
- Angular - Angular 的 DI 工具使用文档
- injection-js - 把 Agular 的 DI 抽取出来的单独仓库。
- InversifyJS - 目前社区中比较受欢迎的 DI 库,但是感觉用法比较麻烦。
- power-di - 支付宝小程序目前使用的 DI 工具。