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
依赖注入通常都借助第三方框架来实现,实现须要考虑循环依赖,错误处理,容器存储等。
- 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/flinkx
gitee开源项目:https://gitee.com/dtstack_dev_0/flinkx