聊聊IOC中依赖注入那些事 (Dependency inject)
What is Dependency injection
依赖注入定义为组件之间依赖关系由容器在运行期决定,形象的说即由容器动态的将某个依赖关系注入到组件之中在面向对象编程中,我们经常处理的问题就是解耦,控制反转(IoC)就是常用的面向对象编程的设计原则,其中依赖注入是控制反转最常用的实现。目标解决当前类不负责被依赖类实例的创建和初始化。
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();
}
}
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()
存在的问题
- Unit tests 很难写
- 组件不易复用和维护,可扩展性比较低
UserService不应该承载ApiService和LoggerService实例的创建。
如何解决
采用依赖注入,UserService不负责被依赖类的创建和销毁,而是通过外部传入api和logger对象的方式注入。常见依赖注入方式有三种,本文主要以构造器注入为例解释。
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
依赖注入一般都借助第三方框架来实现,实现需要考虑循环依赖,错误处理,容器存储等。
- 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
作者信息

聊聊IOC中依赖注入那些事 (Dependency inject)的更多相关文章
- 我在项目中运用 IOC(依赖注入)--实战篇
上一篇<我在项目中运用 IOC(依赖注入)--入门篇>只是简单的使用 IOC.实际项目使用 IOC 的情景复杂多了,比如说,构造函数有多个参数,有多个类继承同一个接口... Unity都有 ...
- Spring点滴七:Spring中依赖注入(Dependency Injection:DI)
Spring机制中主要有两种依赖注入:Constructor-based Dependency Injection(基于构造方法依赖注入) 和 Setter-based Dependency Inje ...
- 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。
轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战 ...
- 控制反转(Ioc)和依赖注入(DI)
控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基 ...
- 控制反转(IOC)/依赖注入(DI)理解
个人学习笔记,来自Acode. 1.术语 控制反转/反向控制,英文全称“Inversion of Control”,简称IoC. 依赖注入,英文全称“Dependency Injection”,简称D ...
- Ioc容器依赖注入-Spring 源码系列(2)
Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...
- Spring学习-理解IOC和依赖注入
最近刚买了一本介绍ssm框架的书,里面主要对Mybatis.spring.springmvc和redis做了很多的讲解,个人觉得虽然有的内容我看不懂,但是整体上还是不错的.最近正在学习中,一边学习一边 ...
- SSM框架之Spring(3)IOC及依赖注入(基于注解的实现)
Spring(3)IOC及依赖注入(基于注解的实现) 学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样 的,都是要降低程序间的耦合.只是配置的形 ...
- SSM框架之Spring(2)IOC及依赖注入
Spring(2)IOC及依赖注入 基于xml配置文件的实现 1.IOC (控制反转-Inversion Of Control) 控制反转(Inversion of Control,缩写为IoC),是 ...
随机推荐
- Python编写abaqus后处理脚本(学习笔记)
本节内容参考自书籍<Python语言在Abaqus中的应用>,注意:以下代码为伪代码,仅供参考 1.导入必要的模块,加载后处理odb文件 from abaqus import * from ...
- MySQL中MyISAM为什么比InnoDB查询快
大家都知道在MySQL中,MyISAM比InnoDB查询快,但很多人都不知道其中的原理. 今天我们就来聊聊其中的原理,另外也验证下是否MyISAM比InnoDB真的查询快. 在探索其中原理之前,我们先 ...
- [刷题] PTA 查验身份证
题目: 7-63 查验身份证 (15 分) 一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5, ...
- Advanced Archive Password Recovery (ARCHPR) 是一个强大的压缩包密码破解工具,适用于ZIP和RAR档案的高度优化的口令恢复工具。
RAR压缩文件密码破解工具是一款简单易用的RAR文档和ZIP文档密码破解软件,如果你不小心忘了解压密码或是下载的RAR文件需要密码,那么均可以使用本软件进行暴力破解.不管WinRAR /RAR 的密码 ...
- 【转载】基于RedHatEnterpriseLinux V7(RHEL7)下SPEC CPU 2006环境搭建以及测试流程(之一)——介绍、安装准备、安装、config文件以及运行脚本介绍
基于RedHatEnterpriseLinux V7(RHEL7)下SPEC CPU 2006环境搭建以及测试流程(之一)--介绍.安装准备.安装.config文件以及运行脚本介绍 其他 2018-0 ...
- KVM性能优化
一.KVM为什么要调优 性能的损耗是关键.KVM采用全虚拟化技术,全虚拟化要由一个软件来模拟硬件,故有一定的损耗,特别是I/O,因此需要优化.KVM性能优化主要在CPU.内存.I/O这几方面.当然对于 ...
- shell 读取某个目录下的所有文件
#!/bin/shFILE_PATH="xxx" xxx:路径cd $FILE_PATHfor FILE in `ls` do echo $FILE done
- python3 读取txt文件数据,绘制趋势图,matplotlib模块
python3 读取txt文件数据,绘制趋势图 test1.txt内容如下: 时间/min cpu使用率/% 内存使用率/% 01/12-17:06 0.01 7.61 01/12-17:07 0.0 ...
- UCOS明白解析
UCOSII 是一个可以基于 ROM 运行的.可裁减的.抢占式.实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS).为了提供最 ...
- System Verilog MCDF(二)
整形器的接口时序: reg,grant是维持了两个clk的. chid ,length在发送数据期间不可以变化. 第一个data数据必须在start上升沿的同一个clk发送. reg,grant两者之 ...