Header Ads Widget

Responsive Advertisement

Nổi bật

6/recent/ticker-posts

Java Dependency Injection


Mẫu thiết kế Java Dependency Injection cho phép chúng tôi loại bỏ các phần phụ thuộc được mã hóa cứng và làm cho ứng dụng của chúng tôi được liên kết lỏng lẻo, có thể mở rộng và có thể bảo trì. Chúng ta có thể thực hiện dependency injection trong java để chuyển độ phân giải phụ thuộc từ compile-time sang runtime.

Java Dependency injection có vẻ khó nắm bắt về lý thuyết, vì vậy tôi sẽ lấy một ví dụ đơn giản và sau đó chúng ta sẽ xem cách sử dụng mẫu phụ thuộc để đạt được sự liên kết lỏng lẻo và khả năng mở rộng trong ứng dụng.

Giả sử chúng ta có một ứng dụng EmailService để gửi email. Thông thường, chúng tôi sẽ thực hiện điều này như dưới đây.

package com.codetimes90.Java.legacy;

public class EmailService {

       public void sendEmail(String message, String receiver){

              //logic to send email

              System.out.println("Email sent to "+receiver+ " with Message="+message);

       }

}

Lớp EmailService giữ logic để gửi một thông điệp email đến địa chỉ email người nhận. Mã ứng dụng của chúng tôi sẽ như dưới đây.

package com.codetimes90.Java.legacy;

public class MyApplication {

       private EmailService email = new EmailService();

       public void processMessages(String msg, String rec){

              //do some msg validation, manipulation logic etc

              this.email.sendEmail(msg, rec);

       }

}

Mã khách hàng của chúng tôi sẽ sử dụng lớp MyApplication để gửi email sẽ như dưới đây

package com.codetimes90.Java.legacy;

 

public class MyApplication {

       private EmailService email = new EmailService();

       public void processMessages(String msg, String rec){

              //do some msg validation, manipulation logic etc

              this.email.sendEmail(msg, rec);

       }

}



Thoạt nhìn, có vẻ như không có gì sai với việc thực hiện ở trên. Nhưng mã logic trên có những hạn chế nhất định.

Ø Lớp MyApplication có trách nhiệm khởi tạo dịch vụ email và sau đó sử dụng nó. Điều này dẫn đến sự phụ thuộc được mã hóa cứng. Nếu chúng tôi muốn chuyển sang một số dịch vụ email nâng cao khác trong tương lai, nó sẽ yêu cầu thay đổi mã trong lớp MyApplication. Điều này làm cho ứng dụng của chúng tôi khó mở rộng và nếu dịch vụ email được sử dụng trong nhiều lớp thì điều đó còn khó hơn.

Ø Nếu chúng tôi muốn mở rộng ứng dụng của mình để cung cấp một tính năng nhắn tin bổ sung, chẳng hạn như SMS hoặc tin nhắn Facebook thì chúng tôi sẽ cần phải viết một ứng dụng khác cho điều đó. Điều này sẽ liên quan đến các thay đổi mã trong các lớp ứng dụng và cả trong các lớp máy khách.

Ø Việc kiểm tra ứng dụng sẽ rất khó khăn vì ứng dụng của chúng tôi đang trực tiếp tạo phiên bản dịch vụ email. Không có cách nào chúng ta có thể mô phỏng các đối tượng này trong các lớp thử nghiệm của mình.

Người ta có thể tranh luận rằng chúng ta có thể loại bỏ việc tạo cá thể dịch vụ email khỏi lớp MyApplication bằng cách có một hàm tạo yêu cầu dịch vụ email làm đối số.

public class MyApplication {     

       private EmailService email = null;

      

       public MyApplication(EmailService svc){

              this.email=svc;

       }      

       public void processMessages(String msg, String rec){

              //do some msg validation, manipulation logic etc

              this.email.sendEmail(msg, rec);

       }

}


Nhưng trong trường hợp này, chúng tôi đang yêu cầu các ứng dụng khách hoặc các lớp thử nghiệm khởi tạo dịch vụ email không phải là một quyết định thiết kế tốt.

Bây giờ chúng ta hãy xem làm thế nào chúng ta có thể áp dụng mô hình java dependency injection để giải quyết tất cả các vấn đề với việc triển khai ở trên. Dependency Injection trong java yêu cầu ít nhất những điều sau:

Ø Các thành phần dịch vụ nên được thiết kế với lớp cơ sở hoặc interface. Tốt hơn là nên thích các interface hoặc các abstract class sẽ xác định hợp đồng cho các dịch vụ.

Ø Các lớp Consumer nên được viết dưới dạng service interface.

Ø Các lớp Injector sẽ khởi tạo các dịch vụ và sau đó là các lớp Consumer.

Java Dependency Injection - Các thành phần dịch vụ

Đối với trường hợp của chúng tôi, chúng tôi có thể có MessageService điều đó sẽ tuyên bố hợp đồng triển khai dịch vụ.

package com.codetimes90.java.dependencyinjection.service;

public interface MessageService {

       void sendMessage(String msg, String rec);

}


Bây giờ giả sử chúng ta có các dịch vụ Email và SMS triển khai các interface trên.

package com.codetimes90.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);

       }

}




package com.codetimes90.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);

       }

}


Các dependency injection java services của chúng tôi đã sẵn sàng và bây giờ chúng tôi có thể viết lớp người tiêu dùng của mình.

Java Dependency Injection - Service Consumer

Chúng tôi không bắt buộc phải có interface cơ sở cho các lớp Consumer nhưng tôi sẽ có một Consumer interface khai báo hợp đồng cho các lớp Consumer.

package com.codetimes90.java.dependencyinjection.consumer;

public interface Consumer {

       void processMessages(String msg, String rec);

}


Việc triển khai lớp Consumer của tôi giống như bên dưới.

package com.codetimes90.java.dependencyinjection.consumer;

import com.codetimes90.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){

              //do some msg validation, manipulation logic etc

              this.service.sendMessage(msg, rec);

       }

}

Lưu ý rằng lớp application của chúng tôi chỉ đang sử dụng dịch vụ. Nó không khởi tạo dịch vụ dẫn đến "tách các mối quan tâm " tốt hơn . Ngoài ra, việc sử dụng interface service cho phép chúng tôi dễ dàng kiểm tra ứng dụng bằng cách mô phỏng MessageService và ràng buộc các dịch vụ trong thời gian chạy thay vì thời gian biên dịch.

Bây giờ chúng ta đã sẵn sàng để viết java dependency injector classes sẽ khởi tạo dịch vụ và cả các lớp Consumer

Java Dependency Injection - Injectors Classes

Hãy có một interface MessageServiceInjector với khai báo phương thức trả về lớp Consumer.

package com.codetimes90.java.dependencyinjection.injector;

import com.codetimes90.java.dependencyinjection.consumer.Consumer;

public interface MessageServiceInjector {

       public Consumer getConsumer();

}

Bây giờ đối với mọi dịch vụ, chúng ta sẽ phải tạo các lớp injection như bên dưới.

package com.codetimes90.java.dependencyinjection.injector;

import com.codetimes90.java.dependencyinjection.consumer.Consumer;

import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;

import com.codetimes90.java.dependencyinjection.service.EmailServiceImpl;

 

public class EmailServiceInjector implements MessageServiceInjector {

       @Override

       public Consumer getConsumer() {

              return new MyDIApplication(new EmailServiceImpl());

       }

}

package com.codetimes90.java.dependencyinjection.injector;

import com.codetimes90.java.dependencyinjection.consumer.Consumer;

import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;

import com.codetimes90.java.dependencyinjection.service.SMSServiceImpl;

 

public class SMSServiceInjector implements MessageServiceInjector {

       @Override

       public Consumer getConsumer() {

              return new MyDIApplication(new SMSServiceImpl());

       }

}

Bây giờ chúng ta hãy xem các ứng dụng khách của chúng ta sẽ sử dụng ứng dụng với một chương trình đơn giản như thế nào.

package com.codetimes90.java.dependencyinjection.test;

import com.codetimes90.java.dependencyinjection.consumer.Consumer;

import com.codetimes90.java.dependencyinjection.injector.EmailServiceInjector;

import com.codetimes90.java.dependencyinjection.injector.MessageServiceInjector;

import com.codetimes90.java.dependencyinjection.injector.SMSServiceInjector;

public class MyMessageDITest {

       public static void main(String[] args) {

              String msg = "Hi Lucifer";

              String email = "abc@gmail.com";

              String phone = "0123456789";

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

       }

}


Như bạn có thể thấy rằng các lớp ứng dụng của chúng tôi chỉ chịu trách nhiệm về việc sử dụng dịch vụ. Các lớp dịch vụ được tạo trong bộ phun. Ngoài ra, nếu chúng ta phải mở rộng thêm ứng dụng của mình để cho phép nhắn tin trên facebook, chúng ta sẽ chỉ phải viết các lớp Service và lớp bộ tiêm.

Vì vậy, việc triển khai phụ thuộc đã giải quyết được vấn đề với phụ thuộc được mã hóa cứng và giúp chúng tôi làm cho ứng dụng của mình trở nên linh hoạt và dễ dàng mở rộng. Bây giờ, hãy xem chúng ta có thể kiểm tra lớp ứng dụng của mình dễ dàng như thế nào bằng cách giả lập các lớp dịch vụ và bộ phun. 

Java Dependency Injection - JUnit Test Case với Mock Injector và Service


package com.codetimes90.java.dependencyinjection.test;

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

 

import com.codetimes90.java.dependencyinjection.consumer.Consumer;

import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;

import com.codetimes90.java.dependencyinjection.injector.MessageServiceInjector;

import com.codetimes90.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("Message Service");            

                                  }

                           });

                     }

              };

       }   

       @Test

       public void test() {

              Consumer consumer = injector.getConsumer();

              consumer.processMessages("Hi Lucy", "abc@gmail.com");

       }     

       @After

       public void tear(){

              injector = null;

       }

}


Như bạn có thể thấy rằng tôi đang sử dụng các lớp ẩn danh để giả lập các lớp dịch vụ và bộ tiêm và tôi có thể dễ dàng kiểm tra các phương thức ứng dụng của mình. Tôi đang sử dụng JUnit 4 cho lớp thử nghiệm ở trên, vì vậy hãy đảm bảo rằng nó nằm trong đường dẫn xây dựng dự án của bạn nếu bạn đang chạy lớp thử nghiệm phía trên.

Chúng tôi đã sử dụng các hàm tạo để đưa các phụ thuộc vào các lớp ứng dụng, một cách khác là sử dụng phương thức setter để đưa các phụ thuộc vào các lớp ứng dụng. Đối với việc tiêm phụ thuộc phương thức setter, lớp ứng dụng của chúng ta sẽ được triển khai như bên dưới.

package com.codetimes90.java.dependencyinjection.consumer;

import com.codetimes90.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);

       }

}

package com.codetimes90.java.dependencyinjection.injector;

import com.codetimes90.java.dependencyinjection.consumer.Consumer;

import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;

import com.codetimes90.java.dependencyinjection.service.EmailServiceImpl;

 

public class EmailServiceInjector implements MessageServiceInjector {

       @Override

       public Consumer getConsumer() {

              MyDIApplication app = new MyDIApplication();

              app.setService(new EmailServiceImpl());

              return app;

       }

}

Một trong những ví dụ tốt nhất của việc tiêm phụ thuộc setter là 
Struts2 Servlet API Aware interfaces.

Việc sử dụng phương thức tiêm phụ thuộc dựa trên Constructor hay dựa trên setter là một quyết định thiết kế và tùy thuộc vào yêu cầu của bạn. Ví dụ: nếu ứng dụng của tôi không thể hoạt động mà không có lớp dịch vụ thì tôi muốn DI dựa trên phương thức khởi tạo hoặc nếu không, tôi sẽ sử dụng DI dựa trên phương thức setter để chỉ sử dụng nó khi nó thực sự cần thiết.

Dependency Injection trong Java là một cách để đạt được Inversion of control ( IoC ) trong ứng dụng của chúng ta bằng cách di chuyển các đối tượng ràng buộc từ thời gian biên dịch sang thời gian chạy. Chúng ta có thể đạt được IoC thông qua Factory PatternTemplate Method Design PatternStrategy Pattern và Định vị dịch vụ.

Spring Dependency InjectionGoogle Guice Java EE CDI tạo điều kiện thuận lợi cho quá trình chèn phụ thuộc thông qua việc sử dụng
 Java Reflection API và java annotations. Tất cả những gì chúng ta cần là chú thích field, constructor hoặc phương thức setter và định cấu hình chúng trong các tệp hoặc lớp xml cấu hình. 

Lợi ích của Java Dependency Injection

Một số lợi ích của việc sử dụng Dependency Injection trong Java là:

Ø Tách các mối quan tâm

Ø Boilerplate Code giảm thiểu trong các lớp ứng dụng vì tất cả công việc khởi tạo các phần phụ thuộc được xử lý bởi thành phần bộ phun

Ø Các thành phần có thể định cấu hình giúp ứng dụng có thể dễ dàng mở rộng

Ø Kiểm thử đơn vị dễ dàng với các đối tượng giả 

Nhược điểm của Java Dependency Injection

Java Dependency injection cũng có một số nhược điểm:

Ø Nếu lạm dụng quá mức, nó có thể dẫn đến các vấn đề bảo trì vì ảnh hưởng của các thay đổi được biết đến trong thời gian chạy.

Ø Chèn phụ thuộc trong java ẩn các phụ thuộc lớp dịch vụ có thể dẫn đến lỗi thời gian chạy mà có thể đã mắc phải tại thời điểm biên dịch.

Bài viết này được dịch lại từ: journaldev.com. Cảm ơn bạn đã xem bài viết!

Đăng nhận xét

0 Nhận xét