1. Giới thiệu
Ở bài viết trước Spring Boot Series - Core Concepts (Part 1), chúng ta đã tìm hiểu về 2 khái niệm tight coupling
và loosely-coupled
.
Trong bài viết hôm nay, ta sẽ tiếp tục khai vấn 2 khái niệm gây nhức nhối và thương nhớ cho rất nhiều developer. Để làm việc được với Spring và hệ sinh thái quanh nó, thì việc đầu tiên, tiên quyết, duy nhất bạn cần làm đó là thấu hiểu định nghĩa của 2 cái này.
2. Dependency Injection (DI)
Trong tài liệu nó định nghĩa như sau:
Dependency Injection is a design pattern, ...
Vậy bạn có thể hiểu nôm na nó là một phương pháp lập trình, là một thiết kế để bạn có được hiệu quả cao hơn khi code. Trước khi phương pháp này ra đời, bạn vẫn code bình thường, nhưng bây giờ có rồi, đi theo nó sẽ giúp ích nhiều hơn cho việc lập trình của bạn.
Vậy cuối cùng Dependency Injection
nó bảo chúng ta làm gì?
Ta sẽ xem qua ví dụ sau đây:
public class Developer {
private Mac laptop; // mỗi dev sẽ có một laptop khi làm việc
public Developer(){
outfit = new Mac(); // Khi bạn tạo ra 1 dev, bạn cho anh ta 1 chiếc Mac M1 chẳng hạn
}
}
Trước hết, qua đoạn code này, bạn sẽ thấy là khi bạn tạo ra một Developer
, bạn sẽ tạo ra thêm 1 laptop Mac
đi kèm với cô gái đó. Lúc này, Mac
tồn tại mang ý nghĩa là dependency (phụ thuộc) của Developer
.
Như đề cập ở bài viết trước, sự phụ thuộc này sẽ làm code của chúng ta khó thay đổi - Developer
muốn sử dụng một loại laptop khác (Dell, HP,...) cũng như sự xuất hiện lỗi ở class Mac
cũng sẽ vô hình chung ảnh hưởng đến cả class Developer
.
Vấn đề là ở đó, và principle là:
Các Class không nên phụ thuộc vào các kế thừa cấp thấp, mà nên phụ thuộc vào Abstraction (lớp trừu tượng).
Đây cũng là một trong những principle trong SOLID - Dependency Inversion principle.
Nghe hơi khó hiểu. Bây giờ mình thay đoạn code như này:
// Một abstract class cho laptop
public abstract class Laptop {
public void run();
}
// Một object cấp thấp, extend của Laptop
public class Mac extend Laptop {
public void run() {
System.out.println("Đã khởi động MacBook");
}
}
// Bây giờ Developer chỉ phụ thuộc vào Laptop. nếu muốn thay đổi laptop của dev, chúng ta chỉ cần cho Laptop một thể hiện mới.
public class Developer {
private Laptop laptop;
public Developer(){
laptop = new Laptop();
}
}
Tới đây, chúng ta mới chỉ Abtract
hóa thuộc tính của Developer
mà thôi, còn thực tế, Developer
vẫn đang bị gắn với một loại Laptop
duy nhất. Vậy muốn thay đổi loại laptop cho dev, bạn phải làm như nào.
Phải sửa code
thêm chút nữa:
public class Developer {
private Laptop laptop;
public Developer(Laptop anything){
this.lapton = anything; // Tạo ra một dev, với một món laptop tùy biến
// Không bị phụ thuộc quá nhiều vào thời điểm khởi tạo, hay code.
}
}
public class Main {
public static void main(String[] args) {
Laptop dell = new Dell(); // Tạo ra đối tượng laptop loại Dell ở ngoài đối tượng
Developer noradomi = new Developer(dell); // Assign nó vào cho dev khi tạo.
}
}
Với đoạn code ở trên, chúng ta đã gần như tách được Laptop
ra hoàn toàn khỏi Developer
. điều này làm giảm sự phụ thuộc giữa Developer
và Laptop
. Mà tăng tính tùy biến, linh hoạt cho code
.
Bây giờ Developer
sẽ hoạt động với Laptop
mà thôi. Và Laptop
ở đâu ra? Chúng ta tạo ra và đưa nó vào (Inject)
Developer
.
Khái niệm Dependency Injection
từ đây mà ra:
Dependency Injection là việc các Object nên phụ thuộc vào các Abstract Class và thể hiện chi tiết của nó sẽ được Inject vào đối tượng lúc runtime.
Bây giờ muốn Developer
mặc gì khác, bạn chỉ cần tạo một Class kế thừa Laptop
và Inject (Tiêm vào) nó vào Developer
là xong!
Các cách để Inject dependency vào một đối tượng có thể kể đến như sau:
Constructor Injection: Cái này chính là ví dụ của mình, tiêm dependency ngay vào
Contructor
cho tiện.Setter Injection: sử dụng Setter
dev.setLaptop(new Lenovo())
Interface Injection: Mỗi
Class
muốn inject cái gì, thì phảiimplement
mộtInterface
có chứa một hàminject(xx)
(Gần như thay thế cho setter ý bạn). Rồi bạn muốn inject gì đó thì gọi cái hàminject(xx)
ra. Cách này hơi dài và khó cho người mới.
3. Inversion of Control (IoC)
Dependency Injection
giúp chúng ta dễ dàng mở rộng code
và giảm sự phụ thuộc giữa các dependency với nhau. Tuy nhiên, lúc này, khi code bạn sẽ phải kiêm thêm nhiệm vụ Inject dependency (tiêm sự phụ thuộc)
. Thử tưởng tượng một Class
có hàng chục dependency thì bạn sẽ phải tự tay inject từng ý cái. Việc này lại dẫn tới khó khăn trong việc code, quản lý code và dependency.
public static void main(String[] args) {
Outfit tShirt = new TShirt();
Accessories gucci = new GucciAccessories();
HairStyle hair = new KoreanHairStyle();
Developer noradomi = new Developer(tShirt, gucci, hair);
}
Giá như lúc này có thằng làm hộ được chúng ta việc này thì tốt biết mấy.
Bây giờ giả sử, chúng ta định nghĩa trước toàn bộ các dependency
có trong Project, mô tả nó và tống nó vào 1 cái kho
và giao cho một thằng tên là framework
quản lý. Bất kỳ các Class
nào khi khởi tạo, nó cần dependency gì, thì cái framework
này sẽ tự tìm trong kho
rồi inject vào đối tượng thay chúng ta. sẽ tiện hơn phải không?
That it, chính nó, đó cũng chính là nguyên lý chính của Inversion of Control (IOC)
- Đảo chiều sự điều khiển
Nguyên văn Wiki:
Inversion of Control is a programming principle, flow of control within the application is not controlled by the application itself, but rather by the underlying framework.
Khi đó, code chúng ta sẽ chỉ cần như này, để lấy ra 1 đối tượng:
@Override
public void run(String... args) throws Exception {
Developer dev = context.getBean(Developer.class);
}
Đối với Java
thì có một số Framework hỗ trợ chúng ta Inversion of Control (IOC)
, trong đó nổi bật có:
Spring framework
Google Guice
Spring framework là một framework từ những ngày đầu, ra đời để hiện thực ý tưởng Inversion of Control (IOC), tuy nhiên, theo thời gian, Spring lớn mạnh và trở thành một hệ sinh thái rộng lớn phục vụ rất nhiều chức năng trên nền tảng IoC
này.
Google Guice ra đời sau và tập trung vào nhiệm vụ DI thôi.
Cuối cùng, để tránh nhầm lẫn giữa các khái niệm khá giống nhau dưới đây:
Dependency Inversion principle (DIP)
Inversion of Control (IoC)
Dependency Injection (DI)
IoC Container
IoC, DIP là các high-level design principles nên được áp dụng khi xây dựng các classes trong chương trình. Và cũng bởi là principle nên chúng không có implementation cụ thể. Còn DI là một design pattern và IoC Container là Framework.
Tham khảo
https://loda.me/articles/core-gii-thch-dependency-injection-di-v-ioc-bng-ngc-trinh