Dependency Inversion Principle là gì?
Hầu hết các lập trình viên có kinh nghiệm đều hiểu khái niệm ràng buộc mã nguồn trong lập trình, tuy nhiên không phải ai cũng biết về lợi ích khi gỡ bỏ các ràng buộc khởi ứng dụng. Nó giúp cho code ứng dụng dễ đọc, dễ hiểu và cũng dễ dàng trong thay đổi.
Hẳn trong các bạn, đã từng chơi trò rút gỗ Jenga hoặc chí ít là cũng đã từng thấy trò này ở đâu đó. Các miếng gỗ được đặt lên nhau, miếng này trên miếng kia và thực hiện rút các miếng gỗ ở dưới cho đến khi cả tháp gỗ đổ xuống. Nếu việc lập trình cũng như trò chơi rút gỗ này, các module phụ thuộc chặt chẽ với nhau, khi bạn tháo và thay thế các module, rất dễ dẫn đến cả ứng dụng "sụp đổ".
Đừng để lập trình giống như trò chơi rút gỗ Jange.
Như vậy chúng ta cần giảm bớt sự ràng buộc.
Liên kết chặt chẽ (tighly coupled) là một khái niệm trong phát triển phần mềm mà các module/class ràng buộc chặt chẽ vào một module/class khác. Chúng ta có thể giảm sự kết nối này bằng cách định nghĩa các kết nối tiêu chuẩn hay còn gọi là interface, giống như việc thay vì gán dây điện máy sấy tóc trực tiếp vào trong dây hộp điện, chúng ta sử dụng ổ cắm và phích cắm (Xem Ví dụ máy sấy tóc trong bài viết Inversion of Control).
Một hệ thống với các module/class ràng buộc với nhau chặt chẽ như trên, sẽ rất dễ dẫn đến "sụp đổ". Nguyên lý Dependency Inversion giúp giải quyết triệt để vấn đề này. Dependency Inversion Principle (DIP) gồm hai phần:
High-level modules should not depend on low-level modules. Both should depend on abstractions. Mô đun cấp cao không được phụ thuộc vào mô đun cấp thấp, cả hai phải phụ thuộc vào một định nghĩa trìu tượng hay một giao diện (interface). Abstractions should not depend upon details. Details should depend upon abstractions. Định nghĩa trìu tượng (hoặc interface) không được phụ thuộc vào mô đun cụ thể mà ngược lại mô đun cụ thể phải phụ thuộc vào khung trìu tượng.
Hệ thống điện không nên phụ thuộc vào cái máy sấy tóc mà cái máy sấy tóc nên phụ thuộc vào hệ thống điện. Module cấp cao (hệ thống điện) cần một định nghĩa trìu tượng (ổ cắm) để máy sấy tóc có thể cắm vào (máy sấy tóc là một mô đun cụ thể của interface ổ cắm) tức là máy sấy tóc muốn cắm được vào ổ cắm thì nó phải tuân thủ chuẩn ổ cắm, tất cả các thiết bị khác tuân thủ chuẩn ổ cắm đều cắm được vào hệ thống điện. Đó chính là lý do tại sao Dependency Inversion Principle có tên gọi là Nguyên lý đảo ngược sự phụ thuộc.
Giảm bớt ràng buộc bằng cách đảo ngược sự phụ thuộc:
Các đối tượng luôn có những ràng buộc với nhau, không có một hệ thống nào mà các module lại không có những ràng buộc, ở đây chúng ta không phải là tìm cách gỡ bỏ ràng buộc hoàn toàn mà thay vào đó chúng ta giảm bớt các ràng buộc bằng cách đảo ngược sự phụ thuộc. Chúng ta muốn tách hệ thống ra để có thể thay thế các module mà không cần phải thay đổi gì cả.
Nguyên lý Dependency Inversion nói rằng cấp cao không được phụ thuộc vào cấp thấp, ở đây chúng ta đã có một interface để giảm bớt sự ràng buộc, tuy nhiên nếu thiết kế như vậy thì hệ thống điện lại phụ thuộc vào cái máy sấy, nếu có một cái đèn bàn được gắn với ổ cắm loại dẹt thì vỡ mồm rồi.
Đây mới là cái chúng ta cần làm, khi đó mọi thiết bị (module/class) tuân thủ theo chuẩn kết nối (implement từ interface) sẽ hoạt động được với hệ thống. Ok rồi chứ các bạn? Tiếp tục vào phần ví dụ nào?
Ví dụ áp dụng nguyên lý Dependency Inversion
Một ví dụ nhỏ giúp bạn hiểu hơn về Nguyên lý đảo ngược sự phụ thuộc, chúng ta có những người lái xe và những chiếc xe ô tô được định nghĩa trong class Driver và Car như sau:
class Car {
public function run(){
echo 'Brừm brừm...!';
}
}
class Driver {
private $car;
public function __construct() {
$this->car = new Car();
}
public function drive() {
$this->car->run();
}
}
Trong phương thức khởi tạo của Class Driver chúng ta tạo ra một cái xe và gán nó cho người lái xe. Như vậy class Driver ràng buộc chặt chẽ với class Car. Người lái xe có thể lái nhiều loại xe khác nhau, nếu thay thế class Car bằng class khác, ứng dụng sẽ không chạy. Chúng ta tạo ra một định nghĩa trìu tượng (abstract class) hoặc một interface:
interface Car {
public function run();
}
class Mercedes implements Car {
public function run() {
echo 'Mẹc xà đí...!';
}
}
class BMV implements Car {
public function run() {
echo 'Bì èm ví...!';
}
}
class Driver {
private $car;
public function __construct(Car $car) {
$this->car = $car;
}
public function drive() {
$this->car->run();
}
}
Như vậy, những người lái xe và những cái xe ô tô đều phụ thuộc vào interface Car, thứ hai là các class cụ thể như BMV, Mercedes phụ thuộc vào interface Car, do đó nếu có bất kỳ loại xe nào mà thực hiện theo định nghĩa trìu tượng Car thì người lái xe lái được tất.
Lời kết
Dependency Inversion là một nguyên lý rất quan trọng trong phát triển phần mềm, nó cũng tương đối là rõ ràng tuy nhiên còn rất nhiều tranh cãi với các thuật ngữ liên quan như Nguyên lý Inversion of Control, Dependency Injection Pattern, IoC Container, Service Locator... Nếu như hiểu rộng ra, Dependency Inversion Principle là một dạng cụ thể của Inversion of Control, còn Dependency Injection Pattern là một cách thực hiện nguyên lý DIP.
Dependency Injection is about wiring, IoC is about direction, and Dependency Inversion Principle is about shape. IoC là hướng đi và Dependency Inverison là định hình cụ thể của hướng đi còn Dependency Injection là một thực hiện cụ thể.
CÁC BÀI VIẾT KHÁC
An Trần
5 years ago
Phản hồiKami
5 years ago
Phản hồiCulit
5 years ago
Phản hồiTrường Nguyễn
5 years ago
Phản hồiFirebirD
5 years ago
Phản hồidia
5 years ago
Phản hồiTrieu Vu
4 years ago
Phản hồiHQ
4 years ago
Phản hồiManhKM
2 years ago
Phản hồiModule cấp cao ở hình vẽ là những bức tường chứa hệ thống điện. Module cấp thấp ở đây là các thiết bị điện(đèn, máy sấy....) Lớp giao tiến chính là các ổ cắp. --> Ví dụ rất hay và đỉnh luôn, Good job bạn.