We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DI—Dependency Injection,即“依赖注入”:对象之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个对象注入到对象属性之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升对象重用的频率,并为系统搭建一个灵活、可扩展的框架。
容器动态的将某个对象注入到对象属性之中
首先看一下常用依赖注入 (DI)的方式:
function Inject(target: any, key: string){ target[key] = new (Reflect.getMetadata('design:type',target,key))() } class A { sayHello(){ console.log('hello') } } class B { @Inject // 编译后等同于执行了 @Reflect.metadata("design:type", A) a: A say(){ this.a.sayHello() // 不需要再对class A进行实例化 } } new B().say() // hello
TS在编译装饰器的时候,会通过执行__metadata函数多返回一个属性装饰器@Reflect.metadata,它的目的是将需要实例化的service以元数据'design:type'存入reflect.metadata,以便我们在需要依赖注入时,通过Reflect.getMetadata获取到对应的service, 并进行实例化赋值给需要的属性。
__metadata函数
@Reflect.metadata
service
'design:type'
reflect.metadata
Reflect.getMetadata
@Inject编译后代码:
@Inject
var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; // 由于__decorate是从右到左执行,因此, defineMetaData 会优先执行。 __decorate([ Inject, __metadata("design:type", A) // 作用等同于 Reflect.metadata("design:type", A) ], B.prototype, "a", void 0);
即默认执行了以下代码:
Reflect.defineMetadata("design:type", A, B.prototype, 'a');
Inject函数需要做的就是从metadata中获取对应的构造函数并构造实例对象赋值给当前装饰的属性
Inject
metadata
function Inject(target: any, key: string){ target[key] = new (Reflect.getMetadata('design:type',target,key))() }
不过该依赖注入方式存在一个问题:
Inject函数
B.prototype
六大设计原则之开闭原则(避免直接修改类,而应该在类上进行扩展)
TypeDI
typedi 的依赖注入思想是类似的,不过多维护了一个container
container
在了解其container前,我们需要先了解 typedi 中定义的metadata,这里重点讲述一下我所了解的比较重要的几个属性。
id: service的唯一标识
type: 保存service构造函数
value: 缓存service对应的实例化对象
const newMetadata: ServiceMetadata<T> = { id: ((serviceOptions as any).id || (serviceOptions as any).type) as ServiceIdentifier, // service的唯一标识 type: (serviceOptions as ServiceMetadata<T>).type || null, // service 构造函数 value: (serviceOptions as ServiceMetadata<T>).value || EMPTY_VALUE, // 缓存service对应的实例化对象 };
function ContainerInstance() { this.metadataMap = new Map(); //保存metadata映射关系,作用类似于Refect.metadata this.handlers = []; // 事件待处理队列 get(){}; // 获取依赖注入后的实例化对象 ... }
@service
service构造函数
this.metadataMap
@inject
对象
目标
行为
构造函数
静态类型
属性
{ object: target, // 当前等待挂载的类的原型对象 propertyName: propertyName, // 目标属性值 index: index, value: function (containerInstance) { // 行为 var identifier = Reflect.getMetadata('design:type', target, propertyName) return containerInstance.get(identifier); } }
@inject将该对象 push 进一个等待执行的 handlers 待处理数组里,当需要用到对应 service 时执行 value函数 并修改 propertyName。
if (handler.propertyName) { instance[handler.propertyName] = handler.value(this); }
typedi
handlers
Container.get(B)
value函数
metadata.value
相关问题案例可查看: https://stackoverflow.com/questions/55684776/typedi-inject-doesnt-work-but-container-get-does
new B().say() // 将会输出sayHello is undefined Container.get(B).say() // hello word
此处代码依赖TS,不支持JS环境
TS
JS环境
interface Handles { target: any key: string, value: any } interface Con { handles: Handles [] // handlers待处理数组 services: any[] // service数组,保存已实例化的对象 get<T>(service: new () => T) : T // 依赖注入并返回实例化对象 findService<T>(service: new () => T) : T // 检查缓存 has<T>(service: new () => T) : boolean // 判断服务是否已经注册 } var container: Con = { handles: [], // handlers待处理数组 services: [], // service数组,保存已实例化的对象 get(service){ let res: any = this.findService(service) if(res){ return res } res = new service() this.services.push(res) this.handles.forEach(handle=>{ if(handle.target !== service.prototype){ return } res[handle.key] = handle.value }) return res }, findService(service){ return this.services.find(instance => instance instanceof service) }, // service是否已被注册 has(service){ return !!this.findService(service) } } function Inject(target: any, key: string){ const service = Reflect.getMetadata('design:type',target,key) // 将实例化赋值操作缓存到handles数组 container.handles.push({ target, key, value: new service() }) // target[key] = new (Reflect.getMetadata('design:type',target,key))() } class A { sayA(name: string){ console.log('i am '+ name) } } class B { @Inject a: A sayB(name: string){ this.a.sayA(name) } } class C{ @Inject c: A sayC(name: string){ this.c.sayA(name) } } // new B().sayB(). // Cannot read property 'sayA' of undefined container.get(B).sayB('B') container.get(C).sayC('C')
The text was updated successfully, but these errors were encountered:
No branches or pull requests
本文基于自身理解进行输出,目的在于交流学习,如有不对,还望各位看官指出。
DI
DI—Dependency Injection,即“依赖注入”:对象之间依赖关系由容器在运行期决定,形象的说,即由
容器动态的将某个对象注入到对象属性之中
。依赖注入的目的并非为软件系统带来更多功能,而是为了提升对象重用的频率,并为系统搭建一个灵活、可扩展的框架。使用方式
首先看一下常用依赖注入 (DI)的方式:
原理分析
TS在编译装饰器的时候,会通过执行
__metadata函数
多返回一个属性装饰器@Reflect.metadata
,它的目的是将需要实例化的service
以元数据'design:type'
存入reflect.metadata
,以便我们在需要依赖注入时,通过Reflect.getMetadata
获取到对应的service
, 并进行实例化赋值给需要的属性。@Inject
编译后代码:即默认执行了以下代码:
Inject
函数需要做的就是从metadata
中获取对应的构造函数并构造实例对象赋值给当前装饰的属性不过该依赖注入方式存在一个问题:
Inject函数
在代码编译阶段便会执行,将导致B.prototype
在代码编译阶段被修改,这违反了六大设计原则之开闭原则(避免直接修改类,而应该在类上进行扩展)
那么该如何解决这个问题呢,我们可以借鉴一下
TypeDI
的思想。typedi - typedi 是一款支持TypeScript和JavaScript依赖注入工具
typedi 的依赖注入思想是类似的,不过多维护了一个
container
1. metadata
在了解其
container
前,我们需要先了解 typedi 中定义的metadata
,这里重点讲述一下我所了解的比较重要的几个属性。id: service的唯一标识
type: 保存service构造函数
value: 缓存service对应的实例化对象
2. container 作用
@service
会将service构造函数
以metadata形式保存到this.metadataMap
中。@inject
会将依赖注入操作的对象
、目标
、行为
以 object 形式 push 进 handlers 待处理数组。构造函数
与静态类型
及属性
间的映射关系。@inject
将该对象 push 进一个等待执行的 handlers 待处理数组里,当需要用到对应 service 时执行 value函数 并修改 propertyName。相关结论
typedi
中的实例化操作不会立即执行, 而是在一个handlers
待处理数组,等待Container.get(B)
,先对B进行实例化,然后从handlers
待处理数组取出对应的value函数
并执行修改实例化对象的属性值,这样不会影响Class B 自身metadata.value
(typedi 的单例服务特性)。相关问题案例可查看: https://stackoverflow.com/questions/55684776/typedi-inject-doesnt-work-but-container-get-does
实现一个简易版 DI Container
此处代码依赖
TS
,不支持JS环境
The text was updated successfully, but these errors were encountered: