What is Dependency injection

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

What is Dependency

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

When is use Dependency injection

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

  • LoggerServiceApiServiceUserService所依赖
  • ApiServiceUserService所依赖
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 不应该承载ApiServiceLoggerService实例的创建。
如何解决

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

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

Implement simply Dependency injection

预备知识

  • ES6 的平时业务中相对使用较少的特性:Reflect、Proxy、Decorator、Map、Symbol
  • 了解 Dependency injection,ES/TS 装饰器
  • 深入理解 TypeScript - Reflect Metadata
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); // 返回类被装饰函数返回值类型
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) {}
}
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()
Implement simply Dependency injection with container

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 class Dom.

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

FrameWorks

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

作者信息

最新文章

  1. 网络抓包工具-Wireshark学习资料
  2. LeetCode Find All Anagrams in a String
  3. centos 更新linux内核
  4. 【干货】ECS服务器OPENVPN搭建,方便管理所有内网服务器
  5. GWT入门学习之下载安装
  6. 转:RTC搭建android下三层应用程序访问服务器MsSql-客户端
  7. PS CS5
  8. Android设置背景图像重复【整理自网络】
  9. Sybase 导入导出命令
  10. 在 Eclipse 中使用 JSHint 检查 JavaScript 代码
  11. String拼接也有用加号更好的时候
  12. activiti_SpringEnvironment
  13. Python爬虫框架Scrapy获得定向打击批量招聘信息
  14. aJax请求结果中包含form的问题
  15. docker - 修改镜像/容器文件的在宿主机上的存储位置(转)
  16. APP 技术支持
  17. 在mysql配置文件修改sql_mode或sql-mode 怎么办?
  18. SpringBoot统一错误处理
  19. js 复制文本到粘贴板
  20. 基于HTML5 WebGL实现 json工控风机叶轮旋转

热门文章

  1. Linux VMware Tools详解
  2. 搭建LAMP环境部署GLPI资源管理系统
  3. Zabbix5.0服务端部署
  4. centos7 连接打印机
  5. Ansible_编写Playbook文件
  6. Linux_配置加密的https
  7. Linux_配置主DNS服务(基础)
  8. 解决SecureCRTPortable和SecureFXPortable的中文乱码问题
  9. Java 将Excel转为SVG的方法
  10. unity中使用Highlighting System v4.0插件给物体添加高亮