Nguyên lý Dependency Inversion bí quyết tạo ra hệ thống mở

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.

Trò chơi xếp gỗ Jenga

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).

Hệ thống có các module gắn kết chặt chẽ

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:

Dependency inject 1

Module ràng buộc chặt chẽ

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ả.

Cấp cao không được phụ thuộc vào cấp thấp

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.

Kết quả áp dụng nguyên lý Dependency Inversion

Đâ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.

Mối liên hệ giữa IoC, DI, DI Container, IoC Container

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ể.

8 thoughts on “Nguyên lý Dependency Inversion bí quyết tạo ra hệ thống mở

  1. Ví dụ quá hay cho dependency inversion, dễ hiểu và rất thực tế. Mình đã thật sự hiểu về DIP trong SOLID qua bài viết tuyệt vời này, cám ơn rất nhiều

  2. Trong ví dụ có cái ổ cắm và phích cắm, áp sang lập trình nó là những khía niệm gì vậy mọi người 🤔

  3. Trường Nguyễn

    - Edit

    Reply

    khái niệm về module cấp cao và module cấp thấp vẫn thiên về định nghĩa, cần phải thực tế hơn nữa mới dễ hiểu

    1. Trong ví dụ cái máy sấy tóc, hệ thống điện (bức tường) là cái không thể di chuyển được do đó nó phải ít thay đổi, không thể bê cả cái nhà để mang đi sửa điện do đó cái này là module cấp cao. Còn cái máy sấy tóc, đèn bàn thì chúng ta dễ dàng mang đi sửa chữa.

  4. Driver là module cấp cao, Mercedes và BMW là cấp thấp nha thím. Dựa vô câu này “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”.
    Module cấp cao Driver có định nghĩa trìu tượng là Car các module cấp thấp (Mercedes, BMW) muốn gắn vào Driver phải implement Car.

Add Comment