依賴注入(Java Dependency Injection)
例子: 一個(gè)應(yīng)用程序(Computer),使用服務(wù)器(Service)去發(fā)送郵件,實(shí)現(xiàn)的代碼如下:
//屬性注入
class Computer {
//局限: 依賴EmailService,并需要實(shí)例化使用,不利于擴(kuò)展和測(cè)試
private val emailService = EmailService()
fun processMessages(msg: String, rec: String) {
//do some msg validation, manipulation logic etc
emailService.sendEmail(msg, rec)
}
}
EmailService類處理發(fā)送郵件的具體邏輯:
class EmailService {
fun sendEmail(message: String, receiver: String) {
println("Email sent to $receiver with Message=$message")
}
}
用戶(User)使用計(jì)算機(jī)去發(fā)送一封郵件(直接使用main函數(shù)調(diào)用):
fun main() {
val computer = Computer()
computer.processMessages("Hi Pankaj", "pankaj@abc.com")
}
乍一看好像沒(méi)啥問(wèn)題,不過(guò)這樣子實(shí)現(xiàn),有幾點(diǎn)局限性:
- User類負(fù)責(zé)實(shí)例化
EmailService并使用, 這種情況代碼耦合度很高. 如果我們想使用其他的高級(jí)點(diǎn)的計(jì)算機(jī)去發(fā)送郵件,將需要修改Computer類中的代碼, 使用代碼拓展性比較差. - 如果期待
Computer類能提供其他消息發(fā)送的方法,諸如SMS等, 則需要重新寫一個(gè)Computer類,同時(shí)User類中的代碼也需要去改變. - 測(cè)試代碼也變得復(fù)雜起來(lái),因?yàn)?code>Computer是直接實(shí)例化
EmailService.
那換一種做法: 我們移除Computer類直接實(shí)例化EmailService的代碼,改為使用構(gòu)造函數(shù)傳入代替:
/構(gòu)造函數(shù)注入
class Computer(private val emailService: EmailService){
fun processMessages(msg: String, rec: String) {
emailService.sendEmail(msg, rec)
}
}
但這樣一來(lái), 需要``User在使用Computers時(shí)去實(shí)例化EmailService`, 這種做法也不好.
現(xiàn)在可以使用Java依賴注入來(lái)解決上面的問(wèn)題, DI主要包括下列幾個(gè)方法:
-
Service組件應(yīng)該設(shè)計(jì)成接口或者抽象類 -
Consumer類應(yīng)該按照服務(wù)接口來(lái)編寫 -
Injector類負(fù)責(zé)Service和Consumer對(duì)象的實(shí)例化
Service Component
繼續(xù)使用上面的例子, 定義個(gè)MessageService接口
interface MessageService{
fun sendMessage(msg: String, rec: String)
}
然后有兩個(gè)實(shí)現(xiàn)類:
class EmailServiceImpl : MessageService {
override fun sendMessage(msg: String, rec: String) {
println("Email sent to $rec with Message=$msg")
}
}
class SMSServiceImpl : MessageService {
override fun sendMessage(msg: String, rec: String) {
println("SMS sent to $rec with Message=$msg")
}
}
Service Consumer
定義一個(gè)接口, 提供一個(gè)統(tǒng)一處理消息的方法
interface Consumer {
fun processMessages(msg: String, rec: String)
}
Consumer接口實(shí)現(xiàn)類如下:
class DIConsumerImpl(private val service: MessageService) : Consumer {
override fun processMessages(msg: String, rec: String) {
this.service.sendMessage(msg, rec)
}
}
Consumer類僅僅是使用類Service并沒(méi)有去實(shí)例化他, 比較符合separation of concerns原則
Injectors Class
定義一個(gè)方法或者屬性來(lái)獲取Consumer對(duì)象
interface MessageServiceInjector {
val consumer: Consumer
}
對(duì)于每個(gè)Service類,去創(chuàng)建一個(gè)Injector
class EmailServiceInjector : MessageServiceInjector {
override val consumer: Consumer
get() = DIConsumerImpl(EmailServiceImpl())
}
class SMSServiceInjector : MessageServiceInjector {
override val consumer: Consumer
get() = DIConsumerImpl(SMSServiceImpl())
}
這樣子User就可以使用了:
fun main(){
val msg = "Hi Pankaj"
val email = "pankaj@abc.com"
val phone = "4088888888"
var injector: MessageServiceInjector
var app: Consumer
//Send email
injector = EmailServiceInjector()
app = injector.consumer
app.processMessages(msg, email)
//Send SMS
injector = SMSServiceInjector()
app = injector.consumer
app.processMessages(msg, phone)
}
這樣,User類僅僅是使用了Service, Service類的實(shí)例化交給了Injector去實(shí)現(xiàn). 注入使得代碼耦合度降低,并且增加程序的擴(kuò)展性.
上面使用了構(gòu)造函數(shù)的注入方式, 其實(shí)還有一種注入方式為方法注入:
class DIMethodConsumer : Consumer {
private lateinit var service: MessageService
//setter dependency injection
fun setService(service: MessageService) {
this.service = service
}
override fun processMessages(msg: String, rec: String) {
//do some msg validation, manipulation logic etc
this.service.sendMessage(msg, rec)
}
}
class EmailServiceInjector2 : MessageServiceInjector {
override val consumer: Consumer
get() {
val app = DIMethodConsumer()
app.setService(EmailServiceImpl())
return app
}
}
fun main() {
val msg = "Hi Pankaj"
val email = "pankaj@abc.com"
val phone = "4088888888"
val injector: MessageServiceInjector
val app: Consumer
//Send email
injector = EmailServiceInjector2()
app = injector.consumer
app.processMessages(msg, email)
}
到底是使用構(gòu)造函數(shù)注入還是方法注入取決于開發(fā)需求.
依賴注入有著如下的優(yōu)點(diǎn):
- 關(guān)注點(diǎn)分離
- 模板代碼減少
- 利于測(cè)試
同時(shí)有著如下的缺點(diǎn):
- 過(guò)度使用,維護(hù)困難
- 編譯時(shí)期難發(fā)現(xiàn)的運(yùn)行時(shí)錯(cuò)誤