依賴注入(Java Dependency Injection)

依賴注入(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é)ServiceConsumer對(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ò)誤
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容