美文网首页
数栈技术分享:聊聊IOC中依赖注入那些事 (Dependency

数栈技术分享:聊聊IOC中依赖注入那些事 (Dependency

作者: 袋鼠云数栈 | 来源:发表于2021-05-19 13:51 被阅读0次

    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

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

    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

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

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

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

    相关文章

      网友评论

          本文标题:数栈技术分享:聊聊IOC中依赖注入那些事 (Dependency

          本文链接:https://www.haomeiwen.com/subject/ifjpjltx.html