数栈技术分享:前端小姐姐和你聊聊IOC中依赖注入那些事 (Dependency inject)

Posted 数栈DTinsight

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数栈技术分享:前端小姐姐和你聊聊IOC中依赖注入那些事 (Dependency inject)相关的知识,希望对你有一定的参考价值。


Part1: What is Dependency injection

依赖注入定义为组件之间依赖关系由容器在运行期决定,形象的说即由容器动态的将某个依赖关系注入到组件之中在面向对象编程中,我们经常处理的问题就是解耦,控制反转(IoC)就是常用的面向对象编程的设计原则,其中依赖注入是控制反转最常用的实现。目标解决当前类不负责被依赖类实例的创建和初始化。

Part2: What is Dependency


依赖是程序中常见的现象,假设有 A和B都被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();
  }
}

Part3: When is use Dependency injection

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

  • LoggerService被ApiService和UserService所依赖
  • ApiService被UserService所依赖
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()

1、存在的问题

  • Unit tests 很难写
  • 组件不易复用和维护,可扩展性比较低
  • UserService 不应该承载ApiService和LoggerService实例的创建。

2、如何解决

采用依赖注入,UserService不负责被依赖类的创建和销毁,而是通过外部传入api和logger对象的方式注入。常见依赖注入方式有三种,本文主要以构造器注入为例解释。

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

Part4: Implement simply Dependency injection

1、预备知识

  • ES6 的平时业务中相对使用较少的特性:Reflect、Proxy、Decorator、Map、Symbol
  • 了解 Dependency injection,ES/TS 装饰器
  • 深入理解 TypeScript - Reflect Metadata

1)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); // 返回类被装饰函数返回值类型

2)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) {}
}

3)Implement simply Dependency injection

// interface.ts

type Type<T = any> = new (...args: any[]) => T;
export type GenericClassDecorator<T> = (target: T) => void;

// ServiceDecorator.ts

const Service = (): GenericClassDecorator<Type<object>> => {
  return (target: Type<object>) => {};
};

// Injector.ts
export 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
) {
    }
}

@Service
class 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()

4)Implement simply Dependency injection with container

Part5: APIs of InversifyJS with TypeScript

1、使用步骤

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

2、示例

声明接口及类型:

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 

 

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 生成依赖唯一标识符。

Part6: FrameWorks

依赖注入一般都借助第三方框架来实现,实现需要考虑循环依赖,错误处理,容器存储等。

 

数栈是云原生—站式数据中台PaaS,我们在github和gitee上有一个有趣的开源项目:FlinkXFlinkX是一个基于Flink的批流统一的数据同步工具,既可以采集静态的数据,也可以采集实时变化的数据,是全域、异构、批流一体的数据同步引擎。大家喜欢的话请给我们点个star!star!star!

github开源项目:https://github.com/DTStack/flinkx

gitee开源项目:https://gitee.com/dtstack_dev_0/flinkx



以上是关于数栈技术分享:前端小姐姐和你聊聊IOC中依赖注入那些事 (Dependency inject)的主要内容,如果未能解决你的问题,请参考以下文章

依赖注入(DI)和控制反转(IOC)

数栈技术分享:产品经理在线官方解答数栈小知识

数栈技术分享前端篇:TS,看你哪里逃~

干货分享|袋鼠云数栈离线开发平台在小文件治理上的探索实践之路

依赖注入和控制反转的理解,写的太好了。

依赖注入和控制反转的理解,写的太好了。