下面是injector和装饰器Inject的实现。injector的resolve方法在接收到传入的构造函数时,会通过name属性取出该构造函数的名字,比如class Student,它的name属性就是字符串”Student”。再将Student作为key,到dependenciesMap中去取出Student的依赖,至于dependenciesMap中是何时存入的依赖关系,这是装饰器Inject的逻辑,后面会谈到。Student的依赖取出后,由于这些依赖已经是构造函数的引用而非简单的字符串了(比如Notebook、Pencil的构造函数),因此直接使用new语句即可获取这些对象。获取到Student类所依赖的对象之后,如何把这些依赖作为构造函数的参数传入到Student中呢?最简单的莫过于ES6的spread操作符。在不能使用ES6的环境下,我们也可以通过伪造一个构造函数来完成上述逻辑。注意为了使instanceof操作符不失效,这个伪造的构造函数的prototype属性应该指向原构造函数的prototype属性。
var dependenciesMap = {}; var injector = { resolve: function (constructor) { var dependencies = dependenciesMap[constructor.name]; dependencies = dependencies.map(function (dependency) { return new dependency(); }); // 如果可以使用ES6的语法,下面的代码可以合并为一行: // return new constructor(...dependencies); var mockConstructor: any = function () { constructor.apply(this, dependencies); }; mockConstructor.prototype = constructor.prototype; return new mockConstructor(); } }; function Inject(...dependencies) { return function (constructor) { dependenciesMap[constructor.name] = dependencies; return constructor; }; }
injector和装饰器Inject的逻辑完成后,就可以用来装饰class Student并享受依赖注入带来的乐趣了:
// 装饰器的使用非常简单,只需要在类定义的上方添加一行代码 // Inject是装饰器的名字,后面是function Inject的参数 @Inject(Notebook, Pencil, Eraser) class Student { pencil: Pencil; eraser: Eraser; notebook: Notebook; public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) { this.notebook = notebook; this.pencil = pencil; this.eraser = eraser; } public write() { if (!this.notebook || !this.pencil) { throw new Error('Dependencies not provided!'); } console.log('writing...'); } public draw() { if (!this.notebook || !this.pencil || !this.eraser) { throw new Error('Dependencies not provided!'); } console.log('drawing...'); } } var student = injector.resolve(Student); console.log(student instanceof Student); // true student.notebook.printName(); // this is a notebook student.pencil.printName(); // this is a pencil student.eraser.printName(); // this is an eraser student.draw(); // drawing student.write(); // writing
利用装饰器,我们还可以实现一种比较激进的依赖注入,下文称之为RadicalInject。RadicalInject对原代码的侵入性比较强,不一定适合具体的业务,这里也一并介绍一下。要理解RadicalInject,需要对TypeScript装饰器的原理和Array.prototype上的reduce方法理解比较到位。
function RadicalInject(...dependencies){ var wrappedFunc:any = function (target: any) { dependencies = dependencies.map(function (dependency) { return new dependency(); }); // 使用mockConstructor的原因和上例相同 function mockConstructor() { target.apply(this, dependencies); } mockConstructor.prototype = target.prototype; // 为什么需要使用reservedConstructor呢?因为使用RadicalInject对Student方法装饰之后, // Student指向的构造函数已经不是一开始我们声明的class Student了,而是这里的返回值, // 即reservedConstructor。Student的指向变了并不是一件不能接受的事,但是如果要 // 保证student instanceof Student如我们所期望的那样工作,这里就应该将 // reservedConstructor的prototype属性指向原Student的prototype function reservedConstructor() { return new mockConstructor(); } reservedConstructor.prototype = target.prototype; return reservedConstructor; } return wrappedFunc; }
使用RadicalInject,原构造函数实质上已经被一个新的函数代理了,使用上也更为简单,甚至都不需要再有injector的实现:
@RadicalInject(Notebook, Pencil, Eraser) class Student { pencil: Pencil; eraser: Eraser; notebook: Notebook; public constructor() {} public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) { this.notebook = notebook; this.pencil = pencil; this.eraser = eraser; } public write() { if (!this.notebook || !this.pencil) { throw new Error('Dependencies not provided!'); } console.log('writing...'); } public draw() { if (!this.notebook || !this.pencil || !this.eraser) { throw new Error('Dependencies not provided!'); } console.log('drawing...'); } } // 不再出现injector,直接调用构造函数 var student = new Student(); console.log(student instanceof Student); // true student.notebook.printName(); // this is a notebook student.pencil.printName(); // this is a pencil student.eraser.printName(); // this is an eraser student.draw(); // drawing student.write(); // writing