美文网首页
依赖注入(Java Dependency Injection)

依赖注入(Java Dependency Injection)

作者: Showdy | 来源:发表于2019-05-15 00:17 被阅读0次

    依赖注入(Java Dependency Injection)

    例子: 一个应用程序(Computer),使用服务器(Service)去发送邮件,实现的代码如下:

    //属性注入
    class Computer {
    
        //局限: 依赖EmailService,并需要实例化使用,不利于扩展和测试
        private val emailService = EmailService()
    
        fun processMessages(msg: String, rec: String) {
            //do some msg validation, manipulation logic etc
            emailService.sendEmail(msg, rec)
        }
    }
    

    EmailService类处理发送邮件的具体逻辑:

    class EmailService {
        fun sendEmail(message: String, receiver: String) {
            println("Email sent to $receiver with Message=$message")
        }
    }
    

    用户(User)使用计算机去发送一封邮件(直接使用main函数调用):

    fun main() {
        val computer = Computer()
        computer.processMessages("Hi Pankaj", "pankaj@abc.com")
    }
    

    乍一看好像没啥问题,不过这样子实现,有几点局限性:

    • User类负责实例化EmailService并使用, 这种情况代码耦合度很高. 如果我们想使用其他的高级点的计算机去发送邮件,将需要修改Computer类中的代码, 使用代码拓展性比较差.
    • 如果期待Computer类能提供其他消息发送的方法,诸如SMS等, 则需要重新写一个Computer类,同时User类中的代码也需要去改变.
    • 测试代码也变得复杂起来,因为Computer是直接实例化EmailService.

    那换一种做法: 我们移除Computer类直接实例化EmailService的代码,改为使用构造函数传入代替:

    /构造函数注入
    class Computer(private val emailService: EmailService){
        fun processMessages(msg: String, rec: String) {
            emailService.sendEmail(msg, rec)
        }
    }
    

    但这样一来, 需要``User在使用Computers时去实例化EmailService`, 这种做法也不好.

    现在可以使用Java依赖注入来解决上面的问题, DI主要包括下列几个方法:

    • Service组件应该设计成接口或者抽象类
    • Consumer类应该按照服务接口来编写
    • Injector类负责ServiceConsumer对象的实例化

    Service Component

    继续使用上面的例子, 定义个MessageService接口

    interface MessageService{
        fun sendMessage(msg: String, rec: String)
    }
    

    然后有两个实现类:

    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

    定义一个接口, 提供一个统一处理消息的方法

    interface Consumer {
    
        fun processMessages(msg: String, rec: String)
    }
    

    Consumer接口实现类如下:

    class DIConsumerImpl(private val service: MessageService) : Consumer {
    
        override fun processMessages(msg: String, rec: String) {
            this.service.sendMessage(msg, rec)
        }
    }
    

    Consumer类仅仅是使用类Service并没有去实例化他, 比较符合separation of concerns原则

    Injectors Class

    定义一个方法或者属性来获取Consumer对象

    interface MessageServiceInjector {
    
        val consumer: Consumer
    }
    

    对于每个Service类,去创建一个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类的实例化交给了Injector去实现. 注入使得代码耦合度降低,并且增加程序的扩展性.

    上面使用了构造函数的注入方式, 其实还有一种注入方式为方法注入:

    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)
    }
    
    

    到底是使用构造函数注入还是方法注入取决于开发需求.

    依赖注入有着如下的优点:

    • 关注点分离
    • 模板代码减少
    • 利于测试

    同时有着如下的缺点:

    • 过度使用,维护困难
    • 编译时期难发现的运行时错误

    相关文章

      网友评论

          本文标题:依赖注入(Java Dependency Injection)

          本文链接:https://www.haomeiwen.com/subject/ddzyaqtx.html