Java – Dependency Injection

Dependency Injection cho phép chúng ta loại bỏ những Dependecy bị hard-code và tạo cho ứng dụng chúng ta gắn kết lỏng lẻo, có khả năng mở rộng và có khả năng bảo trì. Chúng ta có thể thực thi Dependency Injection để chuyển việc giải quyết Dependency khi biên dịch sang thời gian chạy

Dependency Injection thường gây khó hiểu khi đọc lý thuyết, nên chúng ta sẽ cùng nhau tìm hiểu nó thông qua một vài ví dụ đơn giản

Giả sử chúng ta có một ứng dụng sử dụng EmailService để gửi email, thông thường chúng ta sẽ thực hiện như sau

Class EmailService nắm giữ logic gửi thông tin email đến địa chỉ email người nhận. Code của ứng dụng sẽ như sau

Code ở phía client sẽ sử dụng MyApplication class để gửi thông tin email

Thoạt nhìn lần đầu tiên, phần thực hiện code bên trên không có vấn đề gì cả. Nhưng logic code bên trên chắc chắn bị bạn chế

  • MyApplication class có trách nhiệm khởi tạo email service và sử dụng nó. Điều này sẽ dẫn đến việc hard-code cho Dependency, nếu chúng ta thay đổi một vài đặc tính nâng cao của email service trong tương lai, nó sẽ dẫn đến việc cần thay đổi code ở trong MyApplication. Điều này làm cho ứng dụng của chúng ta khó mở rộng và nếu email service được sử dụng trong nhiều class thì nó sẽ còn khó khăn hơn.
  • Nếu bạn muốn mở rộng ứng dụng để cung cấp thêm tính năng cho phần gửi tin nhắn như là SMS hoặc Facebook messager thì chúng ta cần phải viết một ứng dụng mới để thực hiện điều này. Điều này dẫn đến việc cần phải thay đổi code trong các class của  application và client.
  • Việc test các application trở nên rất khó khăn bởi vì application đang tạo email service instance trực tiếp, sẽ không có cách nào để mock các object này trong các class test.

Một giả thiết được đưa ra rằng chúng ta có thể xoá phần tạo instance của email service trong class MyApplication bằng cách truyền nó vào trong constructor như một tham số.

Nhưng trong trường hợp này, các class phía client hoặc trong các class test cần phải thực hiện khởi tạo email service mà đó không phải là quyết định tốt.

Chúng ta hãy cùng xem làm cách nào để Dependency Injection của Java có thể giúp chúng ta giải quyết được vấn đề này với các thực thi bên trên.

Dependency Injection trong Java yêu cầu tối thiểu như sau:

  1. Thành phần Service nên được thiết kế như một class cơ sở hay interface. Tốt hơn hết nó nên là abstract class hoặc interface và sẽ định nghĩa các giao kèo cho các Service.
  2. Các class Consumer nên được viết dựa theo interface của Service
  3. Các class Injector đó sẽ được khởi tạo trong Service và sau đó các class Comsumer.

Thành phần Service

Với trường hợp trên, chúng ta có thể có MessageService dùng để khai báo các giao kèo cho các Service thực thi

Tiếp theo chúng ta sẽ có email service và SMS service thực thi dựa vào interface bên trên

Các class Dependency Injection đã sẵn sàng và bây giờ chúng ta cần viết class Consumer

Service Consumer

Không cần thiết phải có interface hoặc abstract cho class Consumer nhưng chúng ta sẽ sử dụng để tạo giao kèo cho các class thực thi

Consumer class sẽ như sau

Chú ý rằng class Application chỉ đang sử dụng Service, nó không khởi tạo service. Việc sử dụng Service interface cho phép chúng ta dễ dàng test ứng dụng bằng cách mock MessageService và đưa vào các Service tại thời điểm chạy hơn là lúc biên dịch

Tiếp theo chúng ta đã sẵn sàng viết các class Injector và các class Consumer

Injector class

Chúng tao tạo một interface MessageServiceInjector với phương thức khai báo trả về Consumer class

Tiếp theo cho tất cả các Service, chúng ta sẽ tạo các class Injector như sau

Hãy cùng xem các ứng dụng Client sử dụng như thế nào

Bạn có thể thấy rằng các class Application chỉ chịu trách nhiệm sử dụng Service. Các class Service được tạo trong các Injector. Ngoài ra nếu chúng ta phải mở rộng ứng dụng thêm nữa để cho phép gửi tin nhắn Facebook, chúng ta sẽ chỉ phải viết các class Service và class Injector

Vì thế phần thực thi Dependency Injection giải quyết được vấn đề hard-code của Dependency và giúp cho việc tạo ứng dụng thêm linh hoạt và dễ mở rộng. Tiếp theo hãy cùng xem giờ đây việc test ứng dụng đã trở nên dễ dàng như thế nào khi sử dụng mock cho các class Injector và Service.

JUnit Test Case

Bạn có thể thấy rằng chúng ta đang sử dụng  anonymous classes để mock các class Injector và Service, nó giúp cho chúng ta dễ dàng test các phương thức của Application. Code bên trên đang sử dụng JUnit 4 cho class Test, vì thế cần đảm bảo rằng đường dẫn của code trên vẫn hoạt động tốt trong project của bạn.

Chúng ta đã sử dụng constructor để inject các Dependency vào trong class ứng dụng, một cách khác là chúng ta có thể sử dụng phương thức getter, hãy cùng xem code bên dưới đây

Việc sử dụng constructor hoặc setter tuỳ thuộc vào yêu cầu bài toán để quyết định thiết kế. Ví dụ nếu ứng dụng có thể hoạt động hoàn toàn mà không cần class Service thì việc sử dụng Constructor cho DI là tốt hơn, ngoài ra thì sử dụng phương thức setter khi thực sự cần thiết

DI là cách để đạt được  Inversion of control (IoC) trong Application bằng cách chuyển các object ràng buộc nhau tại thời điểm biên dịch sang thời điểm chạy. Chúng ta có thể đạt được IoC bằng cách sử dụng Factory pattern, Template method pattern, Strategy pattern…

Ưu điểm

Sau đây là một vài ưu điểm của DI trong Java:

  • Tách biệt các quan hệ
  • Giảm boilerplate code trong class Application bởi vì tất cả công việc khởi tạo Dependency được xử lý bởi các Injector Component
  • Các Component được cấu hình giúp cho ứng dụng dễ mở rộng
  • Unit test dễ dàng hơn khi sử dụng mock các Object

Nhược điểm

DI có những nhược điểm như sau

  • Nếu lạm dụng nó dẫn đến việc khó bảo trì bởi vì ảnh hưởng của các thay đổi xảy ra tại thời điểm chạy
  • DI ẩn các class Service Dependency có thể dẫn đến lỗi khi chạy mà đến nhẽ có thể bắt khi biên dịch

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

avatar
  Subscribe  
Notify of