Dạo gần đây mình có tìm hiểu về Dependency Inversion thông qua nhiều bài viết trên internet. Tuy nhiên mình có một thắc mắc nhỏ. Đó là vì sao lại đặt tên nguyên lý này là “Dependency Inversion”? Dịch ra tiếng anh thì có nghĩa là “Nghịch đảo phụ thuộc”. Nhưng tại sao lại là nghịch đảo chứ? Ý nghĩa của cái tên này là gì?
Sau một lúc “google thần chưởng” mình đã tìm ra một bài viết tiếng anh giải thích vấn đề này. Mình thấy khá hay nên mạn phép dịch ra chia sẻ cùng mọi người.
Nội dung
Nguyên lý Dependency Inversion thường được mô tả là “Phụ thuộc vào abstract, chứ không nên phụ thuộc vào chi tiết”. Nhưng vậy rốt cuộc là cái gì được “nghịch đảo”?
Nếu chúng ta vẽ biểu đồ lớp cho trường hợp không áp dụng dependency inversion, sau đó áp dụng vào, thì các mũi tên quan hệ phụ thuộc có bị đảo ngược lại không? Hãy thử xem nào.
Tôi mạn phép lấy ví dụ từ đây, trong bài viết của Uncle Bob: butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod.
Hãy tưởng tượng bạn có một class là Copier. Class này nhận input từ bàn phím và gửi đến máy in. Class Copier phụ thuộc vào class Keyboard và class Printer. Class Keyboard dùng để nhận gõ phím từ user và class Printer để in những ký tự đó ra.
public class Copier {
private readonly Keyboard _keyboard;
private readonly Printer _printer;
public Copier(Keyboard keyboard, Printer printer){
_keyboard = keyboard;
_printer = printer;
}
public void Copy(){
int c = _keyboard.Read();
while(!_keyboard.IsEndingCharacter(c)){
_printer.Write(c);
c = _keyboard.Read();
}
}
}

Và giờ chúng ta sẽ áp dụng dependency inversion, tức là thay vì phụ thuộc vào các class cụ thể, Copier sẽ phụ thuộc vào các trừu tượng (abstractions) (hình 2). Các trừu tượng này gồm:
- IReader – Định nghĩa thành phần nhận input.
- IWriter – Định nghĩa thành phần xuất output.
public class Copier{
private readonly IReader _reader;
private readonly IWriter _writer;
public Copier(IReader reader, IWriter writer){
_reader = reader;
_writer = writer;
}
public void Copy(){
int c = _reader.Read();
while(!_reader.IsEndingCharacter(c)){
_writer.Write(c);
c = _reader.Read();
}
}
}

Bây giờ Keyboard và Printer đã được tổng quát hoá thành IReader và IWriter. Copier không còn phụ thuộc vào Keyboard và Printer nữa. Điều này nghĩa là bạn có thể thay thế bằng những thành phần khác, ví dụ như copy từ bàn phím tới một file chẳng hạn.
Có nhiều lợi ích cho việc làm vậy. Copier có thể được tái sử dụng mà không cần tới Keyboard và Printer. Bạn có thể chỉnh sửa 2 class này thoải mái mà không sợ ảnh hưởng tới class Copier, miễn là không thay đổi gì đến 2 phần trừu tượng IReader và IWriter.
Nhưng vậy bạn có thể chỗ nào nghịch đảo giữa 2 biểu đồ không?
Tôi là tôi không thấy rồi đấy.
Nhưng mà hãy thử mô tả quan hệ giữa Copier và các phụ thuộc của nó xem.
Đầu tiên, class Copier phụ vào 2 class có mức trừu tượng thấp (low-level) là Keyboard và Printer.
Sau đó, class Copier phụ thuộc vào 2 module có mức trừu tượng cao (high-level) là IReader và IWriter.
Đến đây thì bạn đoán ra được gì chưa? Cái chỗ chuyển từ thấp (low-level) sang cao (high-level) chính là phần “nghịch đảo” (inversion) trong dependency inversion.
Hoá ra vào cái thời ngày xửa ngày xưa, trước giai đoạn thiết kế hướng đối tượng (Object Oriented Design), người ta cho rằng module high-level phụ thuộc vào module low-level là một phương pháp tốt. Cái tên “Dependency Inversion” được dùng để ám chỉ cho điều này.
Chi tiết hơn đây là nội dung trích dẫn trực tiếp từ nguồn (phần: The Dependency Inversion Principle)
Mọi người có thể thắc mắc tại sao tôi lại sử dụng từ “nghịch đảo”. Đó là vì các phương pháp phát triển phần mềm truyền thống, chẳng hạn như phân tích và thiết kế hướng cấu trúc (Structured Analysis And Design), thường tạo ra các cấu trúc phần mềm trong đó các module high level phụ thuộc vào module low level, các trừu tượng phụ thuộc vào chi tiết. Thật sự thì một trong những mục đích của những phương pháp này là định nghĩa cách phân cấp các chương trình con, trong đó mô tả cách các module high level gọi module low level như thế nào… Vì vậy, cấu trúc phụ thuộc của 1 chương trình hướng đối tượng có thiết kế tốt thì “nghịch đảo” so với cấu trúc phụ thuộc tạo ra từ các phương pháp thủ tục truyền thống.
Lời kết
Như vậy là chúng ta đã vừa lý giải được về nguồn gốc và ý nghĩa của nguyên lý Dependency Inversion. Mong là bài viết này sẽ giúp ích cho bạn trong quá trình tìm hiểu và nâng cao tay nghề. Nếu có thắc mắc đừng ngần ngại cho mình biết ở dưới phần comment nhé!
