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 Transfer
không cần quan tâm 2 classVietcomBank
vàVietinBank
làm gì. - Dễ dàng chuyển đổi các phụ thuộc. Như ta có thể khai báo thay thế
VietcomBank
sangTechcomBank
(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
sangruntime
. - 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ặcabstract class
bị cácClient
phụ thuộc. - Service Implement: là các
implement class
cụ thể củaService
. - Injector: là đối tượng chị trách nhiệm khởi tạo các
Client
vàService Implement
, sau đó dựa vào các khai báo phụ thuộc ởClient
và mapping giữaService
vàService Implement
sẽinject
cácService Implement
và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.