JAVA的依赖注入模式(Dependency Injection Design Pattern in Java)

依赖注入模式可以使我们的代码是松耦合的、可扩展的及可维护性更高的。

 

依赖注入模式似乎是很难掌握和理解的,这里我用一个比较简单点的事例介绍下依赖注入模式。

比如我们有个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();
         app.processMessages( "Hi flybird" , "[email protected]" );
     }
 
}
现在看上面的代码似乎没有什么不对的地方,确实也没有什么不对的地方。但局限性还是有的:
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 email = "[email protected]" ;
         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();
         consumer.processMessages( "Hi Pankaj" , "[email protected]" );
     }
     
     @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)的方式 使定制类从编译期到运行期的实现。

你可能感兴趣的:(DI)