前言
谈到DI,不得不先聊下IoC(控制反转),它只是一种思想,不是具体的技术,一个重要的OOP的法则,能指导如何设计出松耦合的程序。一般情况下我们都是在类内部主动创建依赖对象,从而导致类之间耦合高,难以测试。有了IoC,把创建和查找依赖对象的控制权嫁给容器,由容器进行诸如组合对象,所以对象之间的耦合度就松了,利于功能复用,保持系统灵活。
WHAT
那么,DI是什么,IoC跟DI(依赖注入)又有什么关系呢 ?这个概念是面相对象大师Martin Fowler2004年提出的,只是为了更明确的描述IoC,也可以理解为IoC的更合理的实现。所以两者本质是一样的。依赖注入的目的为了提升组件的复用频率,为系统提供灵活,扩展的平台,而不是为系统带来更多的功能。通过依赖注入,只需要简单的配置,无需任何代码就可以指定目标对象,完成自身的业务逻辑,不需要关心对象谁来创建和销毁。
HOW
在实际页面开发过程中,我们会创建很多service,诸如cache,user,logger等,一般我们会这样使用:
// 在Apage
import logger from '...'
import user from '...'
// 在Bpage
import logger from '...'
import user from '...'
很符合我们的编程习惯,但这种写法有个很大问题,耦合性太高。下面会通过一个示例重构的过程来分析和解决这个问题。
1. 页面A需要一个上报到logcenter的日志服务,我们可能这样编码:
class LoggerSvc {
report(){console.log('logcenter report successfull~')}
}
class APage {
logger = null
constructor(){
this.logger = new LoggerSvc()
}
report(){
this.logger.report()
}
}
const page = new APage()
page.report()
// PRINT: logcenter report successfull~
2. 随着系统迭代,LoggerSvc要扩展新的上报方式,比如扩展Logan上报方式,那我们可以是用OO这样抽象:
interface Logger{
report:() => void
}
class LogCenter implements Logger{
report(){ console.log('logcenter report successfull~')}
}
class LoggerSvc {
private logger:Logger
report(){this.logger.report()}
constructor(){
this.logger= new LogCenter()
}
}
class APage {
loggerSvc = null
constructor(){
this.loggerSvc = new LoggerSvc()
}
report(){
this.loggerSvc.report()
}
}
const page = new APage()
page.report()
// PRINT: logcenter report successfull~
3. 通过抽象,需要扩展一个实现Logger接口的Logan类并把LoggerSvc依赖的logger对象修改成Logan实例。
...
// 新增logan
class Logan implements Logger{
report(){ console.log('logan report successfull~')}
}
...
class LoggerSvc {
private logger:Logger
report(){this.logger.report()}
constructor(){
// 需要改动的地方
this.logger = new Logan()
}
}
...
const page = new APage()
page.report()
//PRINT: logan report successfull~
大家会发现切换Logger的时候需要在LoggerSvc内部完成,造成LoggerSvc与Logan耦合性太强。有没有更好的办法呢?
结合上文提到的IoC思想,把对服务的依赖外置。那么只需要在使用的地方注入实例的方式完成切换。
...
class LoggerSvc {
private logger:Logger
report(){this.logger.report()}
constructor(logger:Logger){
this.logger= logger
}
}
class APage {
loggerSvc = null
constructor(loggerSvc:LoggerSvc){
this.loggerSvc = loggerSvc
}
..
}
...
// 使用Ioc 只需改调用的地方即可完成切换
const logger =new Logan()
// or
const logger = new LogCenter()
const loggerSvc = new LoggerSvc(logger)
const page = new APage(loggerSvc)
page.report()
//PRINT: logcenter report successfull~
// or
//PRINT: logan report successfull~
但是我们仍然需要手动创建和管理依赖,能不能把对象的创建和依赖管理自动化呢?这时候DI就派上了用场。我这边用最熟悉的angular的方式模拟下实现过程。在原来的基础上只需要通过Injectable来注册依赖的服务,Injector来创建和管理依赖容器。是不是手动创建和管理的方式不见了。
import { Injectable, Injector } from '@angular/core'
abstract class Logger {
report(): void { }
}
@Injectable()
class LogCenter extends Logger {
report() { console.log('logcenter report') }
constructor() { super() }
}
@Injectable()
class Logan extends Logger {
report() { console.log('logan report') }
constructor() { super() }
}
@Injectable()
class LoggerService {
logger: Logger
report() {
this.logger.report()
}
constructor(logger: Logger) {
this.logger = logger
}
}
@Injectable()
class APage {
constructor(private loggerService: LoggerService) { }
report() {
this.loggerService.report()
}
}
// 创建容器
const inject = Injector.create({
providers: [{
provide: Logger, useClass: LogCenter, deps: []
}, {
provide: LoggerService, deps: [Logger]
}, {
provide: APage, deps: [LoggerService]
}]
})
const page = inject.get(APage)
page.report()
//PRINT: logcenter report successfull~
只需要修改依赖配置表,可以快速完成切换。
...
const inject = Injector.create({
providers: [{
// useClass换成Logan即可
provide: Logger, useClass: Logan, deps: []
}, {
provide: LoggerService, deps: [Logger]
}, {
provide: APage, deps: [LoggerService]
}]
})
...
//PRINT: logan report successfull~
Angular的DI毕竟跟框架太强。介绍一个通用的DI库InversifyJS,把上面的示例通过它来改造一下。
import 'reflect-metadata'
import { Container, injectable, inject, interfaces } from 'inversify'
// 类型声明
const TYEPS = {
LogCenter: Symbol.for('LogCenter'),
Logan: Symbol.for('Logan'),
LoggerService: Symbol.for('LoggerService'),
APage: Symbol.for('APage'),
FactoryLogger: 'Factory<Logger>'
}
interface Logger {
report: () => void
}
@injectable()
class LogCenter implements Logger {
report() { console.log('logcenter report') }
}
@injectable()
class Logan implements Logger {
report() { console.log('logan report') }
}
@injectable()
class LoggerService {
logger: Logger
report() {
this.logger.report()
}
// 标记注入FactoryLogger工厂,通过运行时判断
constructor(@inject(TYEPS.FactoryLogger) loggerFactory: () => Logger) {
this.logger = loggerFactory()
}
}
@injectable()
class APage {
constructor(@inject(TYEPS.LoggerService) private loggerService: LoggerService) { }
report() {
this.loggerService.report()
}
}
// 创建容器
const container = new Container()
container.bind<Logger>(TYEPS.LogCenter).to(LogCenter)
container.bind<Logger>(TYEPS.Logan).to(Logan)
//为FactoryLogger 绑定Factory 函数
container.bind<interfaces.Factory<Logger>>(TYEPS.FactoryLogger).toFactory<Logger>((context: interfaces.Context) => {
return () => {
// 这个地方完成切换
// 1.使用LogCenter
return context.container.get<Logger>(TYEPS.LogCenter)
// 2.使用Logan
return context.container.get<Logger>(TYEPS.Logan)
};
});
container.bind<LoggerService>(TYEPS.LoggerService).to(LoggerService)
container.bind<APage>(TYEPS.APage).to(APage)
let page = container.get<APage>(TYEPS.APage)
page.report()
最后
通过示例优化重构的过程,帮助大家深入浅出的方式理解什么是DI。其本质上就是对象的控制权进行转移。DI已经是好多复杂的系统解耦的通用方式, 希望大家灵活运用。