數棧技術分享:聊聊IOC中依賴注入那些事 (Dependency inject)

Part1: What is Dependency injection

依賴注入定義爲組件之間依賴關係由容器在運行期決定,形象的說即由容器動態的將某個依賴關係注入到組件之中在面向對象編程中,咱們常常處理的問題就是解耦,控制反轉(IoC)就是經常使用的面向對象編程的設計原則,其中依賴注入是控制反轉最經常使用的實現。目標解決當前類不負責被依賴類實例的建立和初始化。git

Part2: What is Dependency


依賴是程序中常見的現象,假設有 A和B都被C耦合依賴着,在 OOP 編程中依賴無處不在。依賴形式有多種表現形式,好比一個類向另外一個類發消息,一個類是另外一個類的成員,一個類是另外一個類的參數。github

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 層打印日誌來講明web

  • 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()

一、存在的問題

  • Unit tests 很難寫
  • 組件不易複用和維護,可擴展性比較低
  • UserService 不該該承載ApiService和LoggerService實例的建立。

二、如何解決

採用依賴注入,UserService不負責被依賴類的建立和銷燬,而是經過外部傳入api和logger對象的方式注入。常見依賴注入方式有三種,本文主要以構造器注入爲例解釋。express

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

一、預備知識編程

  • 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 支持元編程api

類型獲取app

  • 類型元數據: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 injectiondom

// 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容器存儲相關。async

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

一、使用步驟

  • 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

 

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

相關文章
相關標籤/搜索