博客 / 詳情

返回

數棧技術分享:聊聊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

依賴注入一般都藉助第三方框架來實現,實現需要考慮循環依賴,錯誤處理,容器存儲等。

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上有一個有趣的開源項目:FlinkX,FlinkX是一個基於Flink的批流統一的數據同步工具,既可以採集靜態的數據,也可以採集實時變化的數據,是全域、異構、批流一體的數據同步引擎。大家喜歡的話請給我們點個star!star!star!

github開源項目:https://github.com/DTStack/fl...

gitee開源項目:https://gitee.com/dtstack_dev...

user avatar showmeai2 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.