IoC and DI
- IoC (Inversion of Control) 控制反转模式: 将组件间的依赖关系从程序内部提到外部来管理.
- DI (Dependency Injection) 依赖注入模式: 将组件的依赖通过外部以参数或其他形式注入.
A 依赖 B, 若在 A 中实例化 B, 则会形成 A 与 B 间的高度耦合, 使得 A 可测试性与可维护性变差.
将 A 对 B 的控制权抽离出来, 把控制权反转给第三方 (IoC Container), 实现控制反转 (IoC).
IoC Container 将 B 实例化后, 通过构造函数/接口方法/设置属性/工厂模式等方法注入 A 中, 实现依赖注入 (DI).
Inversion of Control
class Component {
// 构造函数注入.
constructor(dep) {
this.dep = dep
}
// 接口方法注入.
run(context, options = {}) {
const dep1 = context.getDep1()
const dep2 = context.getDep2()
dep1.run()
dep2.run()
}
// 设置属性注入.
getDep(dep) {
this.dep = dep
}
// 工厂模式注入.
static createComponent(dep) {
return new Component(dep)
}
action() {
this.dep.run()
}
}
// IoC.
const s1 = new Service('s1')
const s2 = Container.getService('s2')
const s3 = Context.getDep('s3')
const s4 = Context.getInstance()
// DI.
const c1 = new Component(s1)
c1.action() // s1 run.
c1.getDep(s2)
c1.action() // s2 run.
const c2 = Component.createComponent(s3)
c2.action() // s3 run.
c2.run(s4) // s4 run.
Depends on Abstraction
interface Database {
query: () => void
}
class DbMysql extends Database {
public query() {
console.log('Querying by MySQL')
}
}
class Controller {
private db: Database
public constructor(db: Database) {
this.db = db
}
public action() {
this.db.query()
}
}
const db = new DbMysql()
const c = new Controller(db)
c.action()
Injection Container
import type { IProvider } from './providers'
import * as React from 'react'
class Injector {
private static container = new Map<string, any>()
static resolve<T>(Target: Type<T>): T {
if (Injector.container.has(Target.name))
return Injector.container.get(Target.name)
const tokens = Reflect.getMetadata('design:types', Target) || []
const injections = tokens.map((token: Type<any>): any =>
Injector.resolve(token)
)
const instance = new Target(...injections)
Injector.container.set(Target.name, instance)
return instance
}
}
export interface IProvider<T> {
provide: () => T
}
@injectable()
export class NameProvider implements IProvider<string> {
provide() {
return 'World'
}
}
export class Hello extends React.Component {
private readonly nameProvider: IProvider<string>
render() {
return (
<h1>
Hello
{this.nameProvider.provide()}
!
</h1>
)
}
}