十几行代码实现一个ts依赖注入

栏目: 后端 · 发布时间: 5年前

内容简介:IOC(Inversion of Control)即控制反转,DI(Dependency Injection)即依赖注入。假设我们有一个类当需求达到一定复杂的程度时,我们不能为了一个人穿衣服去从布从纽扣开始从头实现,最好能把所有的需求放到一个工厂或者是仓库,我们需要什么直接从工厂的仓库里面直接拿。

IOC(Inversion of Control)即控制反转,DI(Dependency Injection)即依赖注入。

假设我们有一个类 Humen ,要实例一个 Humen ,我们需要实例一个类 Clothes 。而实例化衣服 Clothes ,我们又需要实例化布 Cloth ,实例化纽扣等等。

当需求达到一定复杂的程度时,我们不能为了一个人穿衣服去从布从纽扣开始从头实现,最好能把所有的需求放到一个工厂或者是仓库,我们需要什么直接从工厂的仓库里面直接拿。

这个时候就需要依赖注入了,我们实现一个IOC容器(仓库),然后需要衣服就从仓库里面直接拿实例好的衣服给人作为属性穿上去。

IOC是一种很好的解耦合思想,在开发中,IoC意味着你设计好的对象交给容器控制,而不是使用传统的方式,在对象内部直接控制。在软件开发中有很好的作用,不仅被应用在JavaEE里,在其它语言里同样适用。

其实总结起来大概就是你需要我,工厂就把我打扮好派我去找你。

实现一个IOC容器

因为key类型不一定是字符型,而且数组遍历比较浪费性能,因此不选择Object和Array而选择Map作为容器的数据结构。

因为目标是实现一个可以懒汉模式的IOC容器,所以可以在类 Injector 里写了两个私有类,一个存token和依赖,一个存token和实例化的依赖实例。

在需要某个依赖实例的时候,先去实例的Map中根据token查找出对应的token的实例,如果没有就从存放依赖类的Map中拿出来实例化之后再放入存放实例的Map。

export class Injector {
  private readonly providerMap: Map<any, any> = new Map();
  private readonly instanceMap: Map<any, any> = new Map();
  public setProvider(key: any, value: any): void {
    if (!this.providerMap.has(key)) this.providerMap.set(key, value);
  }
  public getProvider(key: any): any {
    return this.providerMap.get(key);
  }
  public setInstance(key: any, value: any): void {
    if (!this.instanceMap.has(key)) this.instanceMap.set(key, value);
  }
  public getInstance(key: any): any {
    if (this.instanceMap.has(key)) return this.instanceMap.get(key);
    return null;
  }
  public setValue(key: any, value: any): void {
    if (!this.instanceMap.has(key)) this.instanceMap.set(key, value);
  }
}
复制代码

现在new一下,这个容器就创建出来了。

export const rootInjector = new Injector();
复制代码

实现服务

偷偷借走了ng的 Injectable ,通过类装饰器把类存入容器。

export function Injectable(): (_constructor: any) => any {
  return function (_constructor: any): any {
      rootInjector.setProvider(_constructor, _constructor);
      return _constructor;
  };
}
复制代码

把类作为token,把该类存入provider容器,提供给需要依赖的类。

实现基于注解的属性注入

之所以不实现构造注入,setter注入是因为像ng和react这类框架会对直接对构造函数进行注入或是限制构造函数的参数,为了尽量跨框架跨前后端使用,所以还是用装饰器对属性注入尽量减少侵入性。

属性装饰器和反射能帮我们实现这一功能。

export function Inject(): (_constructor: any, propertyName: string) => any {
  return function (_constructor: any, propertyName: string): any {
    const  propertyType: any = Reflect.getMetadata('design:type', _constructor, propertyName);
    const injector: Injector = rootInjector;

    let providerInsntance = injector.getInstance(propertyType);
    if (!providerInsntance) {
        injector.getProvider(propertyType);
        providerInsntance = new providerClass();
        injector.setInstance(key, providerInsntance);
    }
    _constructor[propertyName] = providerInsntance;

    return (_constructor as any)[propertyName];
  };
}
复制代码

使用Reflect的元数据 Reflect.getMetadata('design:type') 获取属性的类型,并作为token去 injector.getInstance 查询对应的实例,如果有则直接将属性映射为查找到的实例。这样就保证我们每次使用装饰器的属性都会获得单例。

如果没有查询到则去另外一个Map中拿出依赖并实例化存入实例的Map。因为js单线程懒汉模式不存在线程安全这么一说,所以没有选择初始化时就把全部的依赖实例化。

demo

import { Inject, Injectable } from '../injector';

@Injectable()
class Cloth {
    public name: string = '麻布';
}

@Injectable()
class Clothes {
  @Inject() public cloth: Cloth;
}

class Humen {
  @Inject() public clothes: Clothes;
}

const pepe = new Humen();
console.log(pepe);
// {
//   clothes: {
//      cloth: {
//        name: '麻布'
//      }
//   }
//}
复制代码

最后我们可以直接new pepe,不需要关心pepe穿的衣服 Clothes 和衣服需要的布料 Cloth

最后推荐结合 rxjs 做响应式编程。 ng大法好!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员2010精华本

程序员2010精华本

程序员杂志社 / 电子工业 / 2011-1 / 49.00元

《程序员(2010精华本)》主要内容:《程序员》创刊10年来,每年末编辑部精心打造的“合订本”已经形成一个品牌,得到广大读者的认可和喜爱。今年,《程序员》杂志内容再次进行了优化整合,除了每期推出的一个大型专题策划,各版块也纷纷以专题、策划的形式,将每月的重点进行了整合,让内容非常具有凝聚力,如专题篇、人物篇、实践篇等。另外杂志的版式、色彩方面也有了很大的飞跃,给读者带来耳目一新的阅读体验。一起来看看 《程序员2010精华本》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器