依赖注入是一个很重要的设计模式。 它使用得非常广泛,以至于几乎每个人都把它简称为 DI 。
Angular 有自己的依赖注入框架,离开它,你几乎没办法构建出 Angular 应用。
下面介绍一种简单的(只有100行左右代码)实现类似Angular依赖注入的方式,先看一个例子
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 @Injectable ()export class AService { constructor ( ) { } public doSomething ( ) { console .log ('this is AService::doSomething' ); } } @Injectable ()export class BService { constructor (private readonly a: AService ) { } public doSomething ( ) { this .a .doSomething (); console .log ('this is BService::doSomething' ); } } @Module ({ providers : [ AService , BService , ], }) export class SomeModule {}
在上面的例子中,我们创建了两个Service
,其中BService
依赖于AService
,那么BService
可以在其构造函数中声明其依赖,我们需要一种方法去自动将AService
的实例注入到BService
的私有只读变量a
中,接下来介绍实现的步骤。
首先我们应该对Typescript
进行配置,使其支持Javascript
的装饰器(或者说是注解),下面是我的配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "compilerOptions" : { "lib" : [ "dom" , "es2015" ] , "target" : "es5" , "module" : "commonjs" , "outDir" : "./dist" , "strict" : true , "esModuleInterop" : true , "experimentalDecorators" : true , "emitDecoratorMetadata" : true } }
其中,experimentalDecorators
表示为ES装饰器启用实验支持 ,emitDecoratorMetadata
表示在源代码中为装饰声明产生类型的元数据 ,配置好了这两项之后,我们才能使用Typescript
的装饰器,接下来我们需要安装依赖reflect-metadata
,用于读取和设置元数据。
@Injectable的实现 @Injectable
是一个装饰器,它标识被装饰的类是一个Provider
,它的声明方式如下
1 2 3 4 5 export function Injectable(): ClassDecorator { return (target) => { }; }
我们在此装饰器中什么都不做,他只起到一个标识的作用。
@Module的实现 @Module
是一个装饰器,它标识被装饰的类是一个Module
,它的声明方式如下
1 2 3 4 5 6 7 8 9 const DI_IMPORTS_SYMBOL = Symbol('di:imports') const DI_PROVIDERS_SYMBOL = Symbol('di:providers') export function Module(options: { imports?: Array<any>, providers?: Array<any> }): ClassDecorator { return (target) => { Reflect.defineMetadata(DI_IMPORTS_SYMBOL, new Set(options.imports || []), target); Reflect.defineMetadata(DI_PROVIDERS_SYMBOL, new Set(options.providers || []), target); } }
我们使用Set
来存储一个Module
作用域中它所声明的Providers
和它所引入的其他模块。
Factory的实现 我们希望达到的目的是,在使用时可以通过Factory.create(SomeModule)
来获取一个Module
的实例,然后通过Module
实例来获取一个Provider
,例如Factory.create(SomeModule).get(BService).doSomething()
,此时应该输出
1 2 3 Factory.create(SomeModule).get(BService).doSomething(); // this is AService::doSomething // this is BService::doSomething
Talk is cheap. Show me the code:
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 export namespace Factory { export function create(module: Type) { const imports: Set<Type> = Reflect.getMetadata(DI_IMPORTS_SYMBOL, module); const providers: Set<any> = Reflect.getMetadata(DI_PROVIDERS_SYMBOL, module); const providersMap = new Map(); const importModules = Array.from(imports).map((importModule) => { let moduleInstance: ModuleInstance = moduleInstances.get(importModule); if(!moduleInstance) { moduleInstance = create(importModule); moduleInstances.set(importModule, moduleInstance); } return moduleInstance; }); const moduleInstance = new ModuleInstance(importModules, providersMap); providers.forEach(provider => { createProvider(provider, providers, moduleInstance); }); return moduleInstance; } function createProvider(provider: any, providers: Set<any>, moduleInstance: ModuleInstance) { let providerInstance = moduleInstance.providers.get(provider); if(providerInstance) { return providerInstance; } const deps: Array<any> = Reflect.getMetadata('design:paramtypes', provider); if(!deps) { throw new Error(`No provider named ${ provider.name }, do yout add @Injectable() to this provider?`); } const args = deps.map(dep => { let depInstance = moduleInstance.providers.get(dep); if(!depInstance) { if(providers.has(dep)) { depInstance = createProvider(dep, providers, moduleInstance); } else { moduleInstance.imports.some(imp => { depInstance = createProvider(dep, new Set(), imp); return !!depInstance; }); } } if(!depInstance) { throw new Error(`can not found provider ${ dep.name }`); } return depInstance; }); providerInstance = new provider(...args); moduleInstance.providers.set(provider, providerInstance); return providerInstance; } export class ModuleInstance { constructor( public imports: Array<ModuleInstance>, public providers: Map<any, any>) { } get<T>(provider: Type<T>) { let instance: T = this.providers.get(provider); if(!instance) { this.imports.some(imp => { instance = imp.get(provider); return !!instance; }); } if(!instance) { throw new Error(`No provider named: ${ provider.name }`); } return instance; } } }
以上就是整个依赖注入的实现了,感兴趣的朋友可以到我的Github上面查看源代码,核心文件就是lib/di.ts
,地址是
https://github.com/hungtcs/light-di/blob/master/lib/di.ts