依赖注入模式可以使我们的代码是松耦合的、可扩展的及可维护性更高的。
依赖注入模式似乎是很难掌握和理解的,这里我用一个比较简单点的事例介绍下依赖注入模式。
比如我们有个EmailService要发送邮件。代码如下:
package
com.test.java;
public
class
EmailService {
public
void
sendEmail(String message, String receiver){
//这里是发送邮件的逻辑
System.out.println(
"Email sent to "
+receiver+
" with Message="
+message);
}
}
EmailService接受一个消息及一个邮件地址作为参数:
MyApplication.java
package
com.test.java;
public
class
MyApplication {
private
EmailService email =
new
EmailService();
public
void
processMessages(String msg, String rec){
//做一下消息验证及逻辑判断等
this
.email.sendEmail(msg, rec);
}
}
客户端将要会用到MyApplication来发送邮件
MyCilentTest.java
package
com.test.java;
public
class
MyCilentTest{
public
static
void
main(String[] args) {
MyApplication app =
new
MyApplication();
}
}
现在看上面的代码似乎没有什么不对的地方,确实也没有什么不对的地方。但局限性还是有的:
1、MyApplication负责初始化email serveice 并且来使用它,这导致代码的依赖性,如果我们想在将来换一些复杂、业务逻辑更强的emailService,这就需要MyApplication代码上的修改。这使得我们的应用程序很难延伸,如果email service用了更多的类,则维护起来更加难。
2、如果我们以后想给我们的应用程序提供额外的消息服务,例如MSM及facebook的信息,那么我们就需要在写应用来实现了,这样就会导致服务端及客户端都需要修改代码。这样是很麻烦的。
3、当我们程序创建了emailService的实例后,测试也是个不简单的事情,也会变得越发受限制。
一种有争论的方式是在MyApplication里面去除产生emailservice实例的代码取而替代的是用构造方法来实现:
MyApplication.java
package
com.test.java;
public
class
MyApplication {
private
EmailService email =
null
;
public
MyApplication(EmailService svc){
this
.email=svc;
}
public
void
processMessages(String msg, String rec){
//做一下消息验证及逻辑判断等
this
.email.sendEmail(msg, rec);
}
}
但这样,我们就是要告诉客户端应用程序或者测试类去初始胡emailservice,这中也不是一个很好地设计方式。
现在我们来用dependency injection(依赖注入模式)来解决上面的问题,dependency injection模式至少需要下面几点要求:
1、Service 组件应该设计成基类或者是借口,这样会更好的使用抽象类或者接口的便利之处,这样更明确了Service。
2、这样用户的类就会依据Service接口。
3、注入的类就会初始化service及客户类。
服务组件(Service Components)
根据我们的要求,我们定义一个MessageService类,他将约定服务的实现:
MessageService.java
package
com.test.java.
dependencyinjection.service;
public
interface
MessageService {
void
sendMessage(String msg, String rec);
}
现在我们需要一个Email 和 SMS services来实现以上的接口
EmailServiceImpl.java
package com.test.java.
dependencyinjection.service
;
public
class
EmailServiceImpl
implements
MessageService {
@Override
public
void
sendMessage(String msg, String rec) {
//logic to send email
System.out.println(
"Email sent to "
+rec+
" with Message="
+msg);
}
}
SMSServiceImpl.java
package com.test.java.
dependencyinjection.service
;
public
class
SMSServiceImpl
implements
MessageService {
@Override
public
void
sendMessage(String msg, String rec) {
//logic to send SMS
System.out.println(
"SMS sent to "
+rec+
" with Message="
+msg);
}
}
我们的Service已经写好了,现在我们来写客户端代码:
用户端服务(Service Consumer)
我们不需要一个基类的接口,但我们需要一个Consumer 接口定义客户端的约定:
Consumer.java
package
com.test.java.dependencyinjection.consumer;
public
interface
Consumer {
void
processMessages(String msg, String rec);
}
下面是我们Consumer的实现:
MyDIApplication.java
package
com.test.java.dependencyinjection.consumer;
import
com.test.java.dependencyinjection.service.MessageService;
public
class
MyDIApplication
implements
Consumer{
private
MessageService service;
public
MyDIApplication(MessageService svc){
this
.service=svc;
}
@Override
public
void
processMessages(String msg, String rec){
//做些消息验证及逻辑处理
this
.service.sendMessage(msg, rec);
}
}
我们的应用程序类只是用Service,但没有初始化Service这样就可以使“关注点分离”。另外用接口可以让我们更容易的测试应用程序,以及绑定MessageService在运行时而不是编译期。
现在我们来编写注入类及并且初始化Service和客户端类。
Injectors Classes
让我们先来写一个MessageServiceInjector类,并且定义一个返回Consumer类的方法:
MessageServiceInjector.java
package com.test.java.
dependencyinjection.injector;
import com.test.java.
dependencyinjection.consumer.Consumer;
public
interface
MessageServiceInjector {
public
Consumer getConsumer();
}
现在我们还需要创建下面的injector类:
EmailServiceInjector.java
package com.test.java.
dependencyinjection.injector;
import
com.test.java.dependencyinjection.consumer.Consumer;
import
com.test.java.dependencyinjection.consumer.MyDIApplication;
import
com.test.java.dependencyinjection.service.EmailServiceImpl;
public
class
EmailServiceInjector
implements
MessageServiceInjector {
@Override
public
Consumer getConsumer() {
return
new
MyDIApplication(
new
EmailServiceImpl());
}
}
SMSServiceInjector.java
package com.test.java.
dependencyinjection.injector;
import
com.test.java.dependencyinjection.consumer.Consumer;
import
com.test.java.dependencyinjection.consumer.MyDIApplication;
import
com.test.java.dependencyinjection.service.SMSServiceImpl;
public
class
SMSServiceInjector
implements
MessageServiceInjector {
@Override
public
Consumer getConsumer() {
return
new
MyDIApplication(
new
SMSServiceImpl());
}
}
现在让我们来看看客户端应用程序怎么来调用:
MyMessageDITest.java
package
com.test.java.dependencyinjection.test;
import
com.test.java.dependencyinjection.consumer.Consumer;
import
com.test.java.dependencyinjection.injector.EmailServiceInjector;
import
com.test.java.dependencyinjection.injector.MessageServiceInjector;
import
com.test.java.dependencyinjection.injector.SMSServiceInjector;
public
class
MyMessageDITest {
public
static
void
main(String[] args) {
String msg =
"Hi Pankaj"
;
String phone =
"4088888888"
;
MessageServiceInjector injector =
null
;
Consumer app =
null
;
//Send email
injector =
new
EmailServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, email);
//Send SMS
injector =
new
SMSServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, phone);
}
}
就像你看到的,我们的应用程序类只负责调用Service。service来创建injector。此外,如果我们在想用facebook的实现,我们只需要再写下Service类和injector类就可以了。
所以依赖注入的实现方式解决了“硬编码”的问题,并且让我们的应用程序是更灵活及可扩展的。
现在让我们来试一下用injector和service来测试我们的应用程序是多么容易的一件事情。
JUnit Test Case 测试 Injector and Service
MyDIApplicationJUnitTest.java
package
com.test.java.dependencyinjection.test;
import
org.junit.After;
import
org.junit.Before;
import
org.junit.Test;
import
com.test.java.dependencyinjection.consumer.Consumer;
import
com.test.java.dependencyinjection.consumer.MyDIApplication;
import
com.test.java.dependencyinjection.injector.MessageServiceInjector;
import
com.test.java.dependencyinjection.service.MessageService;
public
class
MyDIApplicationJUnitTest {
private
MessageServiceInjector injector;
@Before
public
void
setUp(){
//mock the injector with anonymous class
injector =
new
MessageServiceInjector() {
@Override
public
Consumer getConsumer() {
//mock the message service
return
new
MyDIApplication(
new
MessageService() {
@Override
public
void
sendMessage(String msg, String rec) {
System.out.println(
"Mock Message Service implementation"
);
}
});
}
};
}
@Test
public
void
test() {
Consumer consumer = injector.getConsumer();
}
@After
public
void
tear(){
injector =
null
;
}
}
你可以看到我们用了anonymous classes来模拟injector和Service,并且我可以非常容易的用我的方法。上面用的是Junit4测试类,所以确认已经把jar包加入到classpath在你运行以上代码之前。
我们可以用构造器来注入,但实际上用的更多的一种方式是使用setter方式来注入。下面我们来用setter方式注入:
MyDIApplication.java
package
com.test.java.dependencyinjection.consumer;
import
com.test.java.dependencyinjection.service.MessageService;
public
class
MyDIApplication
implements
Consumer{
private
MessageService service;
public
MyDIApplication(){}
//setter dependency injection
public
void
setService(MessageService service) {
this
.service = service;
}
@Override
public
void
processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this
.service.sendMessage(msg, rec);
}
}
EmailServiceInjector.java
package
com.test.java.dependencyinjection.injector;
import
com.test.java.dependencyinjection.consumer.Consumer;
import
com.test.java.dependencyinjection.consumer.MyDIApplication;
import
com.test.java.dependencyinjection.service.EmailServiceImpl;
public
class
EmailServiceInjector
implements
MessageServiceInjector {
@Override
public
Consumer getConsumer() {
MyDIApplication app =
new
MyDIApplication();
app.setService(
new
EmailServiceImpl());
return
app;
}
}
不管使用构造器注入还是用setter方式的模式取决于你的应用程序,例如,如果你的应用程序不能在没有service类的情况下工作,更推荐用构造器模式,否则更适合用setter模式。
依赖注入是一种在我们的应用程序中实现了控制反转Inversion of control (IoC)的方式 使定制类从编译期到运行期的实现。