依赖注入(Java Dependency Injection)
2019-05-15 本文已影响0人
Showdy
依赖注入(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
类负责Service
和Consumer
对象的实例化
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)
}
到底是使用构造函数注入还是方法注入取决于开发需求.
依赖注入有着如下的优点:
- 关注点分离
- 模板代码减少
- 利于测试
同时有着如下的缺点:
- 过度使用,维护困难
- 编译时期难发现的运行时错误