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) và 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é.

Nội dung bài viết:
- Dependency problem in OOP?
- What is Inversion of Control?
- What is Dependency Injection?
- 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ụ:
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 VietcomBank và VietinBank. 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ình là Inversion 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.
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 Transferkhông cần quan tâm 2 classVietcomBankvàVietinBanklàm gì. - Dễ dàng chuyển đổi các phụ thuộc. Như ta có thể khai báo thay thế
VietcomBanksangTechcomBank(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 timesangruntime. - 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
classcó phụ thuộc. - Service: là các
interfacehoặcabstract classbị cácClientphụ thuộc. - Service Implement: là các
implement classcụ thể củaService. - Injector: là đối tượng chị trách nhiệm khởi tạo các
ClientvàService Implement, sau đó dựa vào các khai báo phụ thuộc ởClientvà mapping giữaServicevàService ImplementsẽinjectcácService Implementvào trong cácClient.
Và các bước mà DI sẽ thực hiện:
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, MoneyTransfer và SalaryCalculator là Service 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 VietcomBank và VietinBank 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 Bank và Spring 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.
| |
khi tham chiếu với contructor của MoneyTransfer:
| |
thì Spring Injector sẽ hiểu được sẽ inject VietcomBank vào primaryBank và VietinBank 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 secondaryBank và VietinBank vào primaryBank như sau:
| |
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: IoC và DI 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 IoC và DI 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.