Inversion of Control and Dependency Injection

Vào năm 2012, khi mình vừa mới gia nhập gia đình Global CyberSoft, bước chân vào thế giới lập trình với nhiều bỡ ngỡ 😅 thì 2 khái niệm Inversion of Control (IoC)Dependency Injection (DI) là khó nhằn nhất với mình nhưng nó lại là nền tảng cho những ứng dụng sau này. Cùng mình tìm hiểu vì sao nó lại quan trọng như thế nhé.

Dependency Injection

Nội dung bài viết:

  1. Dependency problem in OOP?
  2. What is Inversion of Control?
  3. What is Dependency Injection?
  4. Implement Salary Transfer by Dependency Injection

1. Dependency problem in OOP

Trong lập trình hướng đối tượng (OOP) có một vấn đề với các chương trình lớn là sự phụ thuộc của các class với nhau trong chương trình. Việc phụ thuộc này làm chương trình rất khó thay đổi, mở rộng cũng như thực hiện Unit Test là không thể với những class bị phụ thuộc vào các class khác.

Khó hiểu quá!!! Mình sẽ lấy ví dụ ở bài Custom Exception để giải thích cho các bạn dễ hiểu hơn nha.

Dưới đây là class diagram của ví dụ: Transfer Transfer class diagram và giả sử ngoài chức năng Money Transfer thì còn có các chức năng khác như: Money Topup, Money Withdraw, Money Exchange Rates… phụ thuộc vào 2 class VietcomBankVietinBank. Nếu có yêu cầu thay đổi VietcomBank thành một ngân hàng khác thì ta cần phải sửa code lại tất cả các class Money Transfer Money Topup, Money Withdraw, Money Exchange Rates… Hãy tưởng tượng nếu chương trình ta có hàng chục, hàng trăm class. Các phụ thuộc có thể là hàng trăm thì chỉ với “một yêu cầu nhỏ” cũng dẫn tới việc thay đổi cấu trúc chương trình rất lớn và tiềm ẩn nhiều rủi ro.

Để giải quyết vấn đề trên người ta đã đưa ra một nguyên tắc lập trìnhInversion of Control

2. What is Inversion of Control?

In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code.
Wikipedia

Choáng rồi phải không ;))

Đấy là định nghĩa của các anh Tây. Còn với mình thì IoC có thể hiểu như một ý tưởng “đảo ngược” lại cách truyền thống khi mà việc khai báo và khởi tạo các phụ thuộc ở các class được chuyển sang cho framework. Nói cách khác chỉ có một nơi làm nhiệm vụ quản lý các phụ thuộc là framework.

IoC explain

Các ưu điểm của IoC:

  • Tách việc thực thi nhiệm vụ cụ thể ra khỏi code điều phối. Như ở ví dụ Money Transfer thì class Money Transfer không cần quan tâm 2 class VietcomBankVietinBank làm gì.
  • Dễ dàng chuyển đổi các phụ thuộc. Như ta có thể khai báo thay thế VietcomBank sang TechcomBank (mới) một cách dễ dàng.
  • Module hóa chương trình.
  • Dễ dàng tạo ra các Testable Class. Mình sẽ giải thích việc này sau ở bài về Unit Test.

Nhưng cũng có kèm những khuyết điểm:

  • Phụ thuộc vào framework.
  • Khó hiểu đối với người mới.
  • Những lỗi khi khai báo phụ thuộc chuyển từ compile time sang runtime.
  • Chương trình khởi động chậm hơn do cần đọc config, khởi tạo các phụ thuộc.

3. What is Dependency Injection?

Ioc là một ý tưởng hay nguyên tắt thôi. Còn việc hiện thực nó thì có nhiều có nhiều cách. Trong số đó cách nổi trội nhất là Dependency Injection (DI). Vậy DI hiện thực IoC như thế nào?

Để hiện thực được IoC thì bên trong DI sẽ có các thành phần sau:

  • Client: là các class có phụ thuộc.
  • Service: là các interface hoặc abstract class bị các Client phụ thuộc.
  • Service Implement: là các implement class cụ thể của Service.
  • Injector: là đối tượng chị trách nhiệm khởi tạo các ClientService Implement, sau đó dựa vào các khai báo phụ thuộc ở Client và mapping giữa ServiceService Implement sẽ inject các Service Implement vào trong các Client.

Và các bước mà DI sẽ thực hiện:

How to Dependency Injection work?

4. Implement Salary Transfer by Dependency Injection

Mình đã convert tất cả code ví dụ Money Transfer từ Java sang Kotlin. Đã hứa có bài hướng dẫn chuyển nhưng tới giờ vẫn chưa làm được 😂.

Về DI framework thì mình chọn Srping nhé. Đơn giản vì nó hiện là framework Java mạnh mẽ nhất. Sau đó update lại file build.gradle.kts giống ở đây. Mình sẽ tập trung vào Spring IoC nên các config khác xin phép bỏ qua.

Vì các class SalaryTransfer, MoneyTransferSalaryCalculatorService Implement của lần lượt các class IocdiApplication, SalaryTransfer nên mình sẽ thêm anotation @Component. Lưu ý mình không khai báo Service vì cảm thấy không cần thiết lắm.

Hai class VietcomBankVietinBank mình không dùng cách thêm anotation @Component vì lúc này có cùng 2 class cùng implement interface BankSpring Injector sẽ không biết chọn class nào để inject vào Client của hai class trên là MoneyTransfer. Vì thế mình khai báo như sau ở class IocdiApplication.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@SpringBootApplication
class IocdiApplication {

  @Bean
  fun primaryBank() = VietcomBank()

  @Bean
  fun secondaryBank() = VietinBank()

}

khi tham chiếu với contructor của MoneyTransfer:

1
2
3
4
5
6
7
@Component
class MoneyTransfer(
  private val primaryBank: Bank,
  private val secondaryBank: Bank
) {
  // bla bla bla bla
}

thì Spring Injector sẽ hiểu được sẽ inject VietcomBank vào primaryBankVietinBank vào secondaryBank. Quá đơn giản phải không ;))

Ta chạy command sau để build

./gradlew clean build

và test thử:

java -jar build/libs/*.jar 11 1000000 EM1
Transfer 11000000 to EM1
VCB only support transfer amount less than 10M
Retry with VTB
VTB transfer success
Transfer Salary success

Bây giờ ta hãy thử hoán đổi vị trí VietcomBank vào secondaryBankVietinBank vào primaryBank như sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@SpringBootApplication
class IocdiApplication {

  @Bean
  fun primaryBank() = VietinBank()

  @Bean
  fun secondaryBank() = VietcomBank()

}

Build và chạy lại thì kết quả là:

Transfer 11000000 to EM1
VTB transfer success
Transfer Salary success

Như vậy ta chỉ cần đổi phần config ở class IocdiApplication là logic của chương trình có thể được thay đổi.

Kết: IoCDI có thể xem khái niệm khó hiểu đối với người mới nhưng mong với bài này bạn sẽ hiểu nó hơn. Có rất nhiều framework mạnh mẽ sử dụng hai khái niệm trên như một phần không thể tách rời. Vì thế đây là đòn bẩy để giúp các bạn học lập trình nhanh hơn. Ngoài ra với các chương trình lớn, bạn sẽ không thể viết được các Testable Class là điều kiện tiên quyết để áp dụng Unit Test mà sau này sẽ cứu mạng bạn rất nhiều lần đó ;)). Tin mình đi! Thời gian bạn bỏ ra để học IoCDI là cực kỳ xứng đáng đấy 💪.

Phần code sample mình update vào repo này nhé. Cảm ơn các bạn đã đọc tới đây.

updatedupdated2021-09-192021-09-19
Load Comments?