IoC?
IoC 英文全称为 Inversion of Control,即 控制反转。控制反转是面向对象编程中的一种原则,用于降低代码之间的耦合度。传统应用程序都是在类的内部主动创建依赖对象,这样将导致类与类之间耦合度非常高,并且不容易测试。有了 IoC 容器之后,可以将创建和查找依赖对象的控制权交给了容器,这样对象与对象之间就是松散耦合了,方便测试与功能复用,整个程序的架构体系也会变得非常灵活。
DI?
DI 英文全称为 Dependency Injection,即 依赖注入。依赖注入是控制反转最常见的一种应用方式,即通过控制反转,在对象创建的时候,自动注入一些依赖对象。
早先接触到上述概念的时候还是在spring中,框架利用注解(@
)实现依赖注入和控制反转,当然在Nestjs
和midway
中我们也能看到,一方面省去了大量的代码实现,另一方面也让各功能逻辑实现了解耦,最重要的是,省去了一系列的实例化及各种绑定代码写起来真的很爽,如下:
// spring
@Controller
public class TestController {
@RequestMapping("/test")
public String test(Map<String,Object> map){
return "hello";
}
}
//Nestjs
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
//midway
import { Context, inject, controller, get, provide } from 'midway';
@provide()
@controller('/test')
export class TestController {
@inject()
ctx: Context;
@get('/')
async index() {
this.ctx.body = `test`;
}
}
想像这样一个场景, A 被 B 和 C 所依赖,而且在构造器需要进行实例化操作,这样的依赖关系在测试中会非常麻烦。这个依赖,一般被叫做 "耦合",而耦合度过高的系统,必然会出现牵一发而动全身的情形。
import {A} from './A';
import {B} from './B';
class C {
consturctor() {
this.a = new A();
this.b = new B(this.a);
}
}
如果使用一个容器来管理我们的模块,这样模块之间的耦合性就降低了,如下:
// injection 是midway官方实现的一个包
import {Container} from 'injection';
import {A} from './A';
import {B} from './B';
const container = new Container();
container.bind(A);
container.bind(B);
class C {
consturctor() {
this.a = container.get('A');
this.b = container.get('B');
}
}
// container 就是 IoC 容器,是依赖注入这种设计模式的一种实现,使得 C 和 A, B 没有了强耦合关系
IoC 容器就像是一个对象池,管理这每个对象实例的信息(Class Definition),所以用户无需关心什么时候创建,当用户希望拿到对象的实例 (Object Instance) 时,可以直接拿到实例,容器会 自动将所有依赖的对象都自动实例化。
对象A获得依赖对象B的过程,获得依赖对象的过程被反转了,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方,对象的主动行为变为了被动行为,控制权颠倒过来了,这也是“控制反转”这个名称的由来,封面图也形象的表达了这个行为,甭管是求职者还是招聘者,需要什么信息平台直接给到
下面两张图也阐述了这个过程:
再来看一段代码,如下:
import { Component, NgZone, OnInit } from '@angular/core';
import { SelfService } from './self.service.ts';
@Component({
selector: 'app-component-test',
templateUrl: './component-test.component.html',
styleUrls: ['./component-test.component.scss']
})
export class TestComponent implements OnInit {
constructor(
private ngZone: NgZone,
private selfService: SelfService
) {}
ngOnInit(): void {
this.update()
}
update (): void {
this.ngZone.run( _ => {
console.log('update')
})
}
}
如果您使用过angular 2及以上的版本,您肯定见过上述代码,在angular中用 服务与依赖注入来实现各个模块、功能逻辑的解耦
private selfService: SelfService
这段代码就是依赖注入的一种形式,这样我们就能在TestComponent
这个类中访问到注入的 NgZone
及自定义的 selfService
,Angular
在底层使用了IoC设计模式,并利用TypeScript
强大的装饰器特性,完成了依赖注入,最大化重用各功能模块,降低代码之间的耦合度
关于IoC
和 DI
之间的关系也有人说,DI就是IoC,我更同意 DI
是 IoC
的一种实现,因为还有一种实现叫DL
,依赖查找,用户自己去是使用 API 进行查找资源和组装对象,有侵入性,已经被抛弃
动手实现一个简单的DI
tip:在typerscript中,类可以这样简写
class Parent {
private name: string;
constructor( name: string ){
this.name = name
}
}
//简写形式如下
class Parennt {
constructor( private name: string ){}
}
前置知识点:
装饰器www.typescriptlang.org reflect-metadatarbuckton.github.io实现:
import 'reflect-metadata';
type Constructor <T = any> = new (...args: any[]) => T;
class LogService {
log(args):void {
console.log(...args)
}
}
class WarnService {
warn(args):void {
console.warn(...args)
}
}
class ErrorService {
error(args):void {
console.error(...args)
}
}
function enject(): ClassDecorator {
return target => {}
}
@enject()
class Demo {
constructor(
private logService: LogService,
private warnService: WarnService,
private errorService: ErrorService
) {
this.test()
}
test () {
this.logService.log('-----log-----')
this.warnService.warn('-----warn-----')
this.errorService.error('-----error-----')
}
}
// IOC容器
function EnjectFactory<T>(target: Constructor<T>): T {
const providers = Reflect.getMetadata('design:paramtypes', target)
const args = providers.map((Provider:Constructor) => new Provider())
return new target(...args)
}
EnjectFactory(Demo)
// 成功打印,即已现实自动注入依赖的实例,当然大家也可以自行实现其他形式的依赖注入,例如:路由、属性等