What is Dependency injection

依赖注入定义为组件之间依赖关系由容器在运行期决定,形象的说即由容器动静的将某个依赖关系注入到组件之中在面向对象编程中,咱们常常解决的问题就是解耦,管制反转(IoC)就是罕用的面向对象编程的设计准则,其中依赖注入是管制反转最罕用的实现。指标解决以后类不负责被依赖类实例的创立和初始化。

what is Dependency

依赖是程序中常见的景象,假如有 AB都被C耦合依赖着,在 OOP 编程中依赖无处不在。依赖模式有多种表现形式,比方一个类向另一个类发消息,一个类是另一个类的成员,一个类是另一个类的参数。

class A {}class B {  classA: A;  constructor() {    this.classA = new A();  }}class C {  classA: A;  classB: B;  constructor() {    this.classA = new A();    this.classB = new B();  }}

when is use Dependency injection

eg: 以用户调用 API 层打印日志来阐明

  • LoggerServiceApiServiceUserService所依赖
  • ApiServiceUserService所依赖
class LoggerService {    constructor() {    }    log(args) {        console.log(args)    }}class ApiService {    constructor (        private readonly logger: LoggerService    ) {        this.logger.log('api constructor')    }    public async getMydata () {        return { name: 'mumiao', hobby: 'focusing in web'}    }}class UserService {    constructor (        private readonly api: ApiService,        private readonly logger: LoggerService    ) {        this.logger.log('user constructor')    }    async getMyhobby () {        const { hobby } = await this.api.getMydata()        return hobby    }}async function Main {    const loggerService = new LoggerService()    const apiService = new ApiService(loggerService)    const userService = new UserService(loggerService, userService)    console.log('my hobby is', await userService.getMyhobby())}Main()
存在的问题
  • Unit tests 很难写
  • 组件不易复用和保护,可扩展性比拟低
  • UserService 不应该承载ApiServiceLoggerService实例的创立。
如何解决

采纳依赖注入,UserService不负责被依赖类的创立和销毁,而是通过内部传入apilogger对象的形式注入。常见依赖注入形式有三种,本文次要以结构器注入为例解释。

const apiService = Injector.resolve < ApiService > ApiService;const userService = Injector.resolve < UserService > UserService;// returns an instance of , with all injected dependencies

implement simply Dependency injection

准备常识

  • ES6 的平时业务中绝对应用较少的个性:Reflect、Proxy、Decorator、Map、Symbol
  • 理解 Dependency injection,ES/TS 装璜器
  • 深刻了解 TypeScript - Reflect Metadata
Reflect
简介

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API,Reflect 的 API 和 Proxy 的 API 一一对应,并且能够函数式的实现一些对象操作。另外,应用 reflect-metadata 能够让 Reflect 反对元编程

类型获取
  • 类型元数据:design:type
  • 参数类型元数据:design:paramtypes
  • 函数返回值类型元数据:design:returntype
Reflect.defineMetaData(metadataKey, metadataValue, target) // 在类上定义元数据Reflect.getMetaData("design:type", target, propertyKey); //返回类被装璜属性类型Reflect.getMetaData("design:paramtypes", target, propertyKey); //返回类被装璜参数类型Reflect.getMetaData("design:returntype", target, propertyKey); // 返回类被装璜函数返回值类型
Decorators
function funcDecorator(target, name, descriptor) {  // target 指 类的prototype name是函数名 descriptor是属性描述符  let originalMethod = descriptor.value;  descriptor.value = function () {    console.log("我是Func的装璜器逻辑");    return originalMethod.apply(this, arguments);  };  return descriptor;}class Button {  @funcDecorator  onClick() {    console.log("我是Func的原有逻辑");  }}
Reflect and Decorators
const Injector = (): ClassDecorator => {  // es7 decorator  return (target, key, descriptor) => {    console.log(Reflect.getMetadata("design:paramtypes", target));    // [apiService, loggerService]  };};@Injector()class userService {  constructor(api: ApiService, logger: LoggerService) {}}
implement simply Dependency injection
// interface.tstype Type<T = any> = new (...args: any[]) => T;export type GenericClassDecorator<T> = (target: T) => void;// ServiceDecorator.tsconst Service = (): GenericClassDecorator<Type<object>> => {  return (target: Type<object>) => {};};// Injector.tsexport const Injector = {  // resolving instances  resolve<T>(target: Type<any>): T {    // resolved injections from the Injector    let injections = Reflect.getMetadata("design:paramtypes", target) || [],      injections = injections.map((inject) => Injector.resolve<any>(inject));    return new target(...injections);  },};

只实现了依赖提取的外围局部,依赖注入还有一个局部是Container容器存储相干。

Resolve Dependency
@Service()class LoggerService {//...}@Service()class ApiService {    constructor (        private readonly logger: LoggerService    ) {    }}@Serviceclass UserService {    constructor (        private readonly api: ApiService,        private readonly logger: LoggerService    ) {    }}async function Main {    // jnject dependencies   const apiService = Injector.resolve<ApiService>(ApiService);   const userService = Injector.resolve<UserService>(UserService);   console.log('my hobby is', await userService.getMyhobby())}Main()
implement simply Dependency injection with container

APIs of InversifyJS with TypeScript

应用步骤

  • Step 1: 申明接口及类型
  • Step 2: 申明依赖应用@injectable & @inject decorators
  • Step 3: 创立并配置一个 Container
  • Step 4: 解析并提取依赖

示例

申明接口及类型:

export interface ILoggerService {}export interface IApiService {}export interface IUserService {}export default TYPES = {  // 惟一依赖标识,倡议应用Symbol.for替换类作为标识符  ILoggerService: Symbol.for("ILoggerService"),  IApiService: Symbol.for("IApiService"),  IUserService: Symbol.for("IUserService"),};

申明依赖:

import 'reflect-metadata'import { injectable, inject } from 'inversify'@injectable()export class LoggerService implements ILoggerService{//...}@injectable()export class ApiService implements IApiService{    protected _logger: LoggerService    constructor (        private @inject(TYPES.ILoggerService) logger: LoggerService    ) {        this._logger = logger    }}

也能够应用 property injection 代替 constructor injection ,这样就不必申明构造函数。

@injectable()export class ApiService implements IApiService {  @inject(TYPES.ILoggerService) private _logger: LoggerService;}
@injectable()export class UserService implements IUserService {    protected _api: ApiService;    protected _logger: LoggerService;    constructor (        private readonly @inject(TYPES.IApiService) api: ApiService,        private readonly @inject(TYPES.ILoggerService) logger: LoggerService    ) {        this._api = api        this._logger = logger    }}

创立并配置一个 Container

...const DIContainer = new container()DIContainer.bind<ApiService>(TYPES.IApiService).toSelf()DIContainer.bind<LoggerService>(TYPES.ILoggerService).toSelf()

解析依赖

import "reflect-matadata";import { UserService } from "./services";import DIContainer from "./container";async function Main() {  const userService: UserService = DIContainer.resolve<UserService>(    UserService  );  console.log("my hobby is", await userService.getMyhobby());}Main();
Classes as identifiers and circular dependencies

An exception:

Error: Missing required @Inject or @multiinject annotation in: argument 0 in class Dom.
import "reflect-metadata";import { injectable } from "inversify";@injectable()class Dom {  public _domUi: DomUi;  constructor(@inject(DomUi) domUi: DomUi) {    this._domUi = domUi;  }}@injectable()class DomUi {  public _dom;  constructor(@inject(Dom) dom: Dom) {    this._dom = dom;  }}@injectable()class Test {  public _dom;  constructor(@inject(Dom) dom: Dom) {    this._dom = dom;  }}container.bind<Dom>(Dom).toSelf();container.bind<DomUi>(DomUi).toSelf();const dom = container.resolve(Test); // Error!

次要起因:decorator 被调用时,类还没有申明,导致inject(undefined),InversifyJS 举荐应用 Symboy.for 生成依赖惟一标识符

inject practice frameWorks

依赖注入个别都借助第三方框架来实现,实现须要思考循环依赖,错误处理,容器存储等。

  • tsyringe:https://github.com/microsoft/tsyringe
    实际:https://github.com/DTStack/molecule
  • InversifyJS: https://github.com/inversify/InversifyJS
    实际:https://codesandbox.io/s/github/inversify/inversify-express-example/tree/master/?file=/BindingDecorators/controller/user.ts

作者信息