SOLID Principles #5 - Dependency Inversion Principle
Bạn đọc đến bài thứ 5 này thì chính xác bạn là 1 fan thực thụ của Ruby rồi. Kết thúc series SOLID Principles, mình xin giới thiệu đến mọi người nguyên lý cuối dùng, đó chính là Dependency Inversion Principle (DIP). Cùng mình bắt đầu tìm hiểu về nguyên lý này nhé! Nguyên lý này sẽ có 2 cách diễn ...
Bạn đọc đến bài thứ 5 này thì chính xác bạn là 1 fan thực thụ của Ruby rồi. Kết thúc series SOLID Principles, mình xin giới thiệu đến mọi người nguyên lý cuối dùng, đó chính là Dependency Inversion Principle (DIP). Cùng mình bắt đầu tìm hiểu về nguyên lý này nhé!
Nguyên lý này sẽ có 2 cách diễn đạt:
- Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. Cả 2 nên phụ thuộc vào những trừu tượng.
- Những trừu tượng không nên phụ thuộc vào những chi tiết. Những chi tiết nên phụ thuộc vào những trừu tượng
Khi nói đến nguyên tắc này, mình nghĩ tốt nhất là đi thẳng vào ví dụ:
Đoạn code sau thể hiện 1 sự vi phạm nguyên lý DIP
class ReportGeneratorManager def initialize(data) @data = data end def call generate_xml_report additional_actions end private attr_reader :data def generate_xml_report XmlRaportGenerator.new(data).generate end def additional_actions ... end end
Chúng ta cùng xem có điều gì không đúng ở đây?
Ta thấy class ReportGenaratorManager (high-level) và XmlReportGenerator (low-level) đều được kết hợp chặt chẽ với nhau. Class High-level phụ thuộc vào những chi tiết (nội dung cụ thể) của class low-level. Hơn nữa, trong trường hợp chúng ta cần thêm 1 loại report generator nữa thì sẽ phải chỉnh sưa class high-level, vì vậy chúng ta bắt buộc phải thực hiện những thay đổi nội dung class high-level chỉ vì những thay đổi của class low-level.
Những gì chúng ta có thể làm ngay tại đây là đảo ngược sự phụ thuộc của các class. Hãy để các chi tiết phụ thuộc vào trừu tượng, không phải là một sự thực hiện cụ thể. Vì Ruby là một ngôn ngữ động, chúng ta có thể sử dụng kỹ thuật duck-typing. Chúng ta không phải tạo bất kỳ abstraction hoặc interface nào vì chúng không tồn tại trong thế giới Ruby.
Dependency Inversion Principle là kỹ thuật duy nhất giúp chúng ta hoàn thành nguyên tắc này
class ReportGeneratorManager def initialize(data, generator = XmlRaportGenerator) @data = data @generator = generator end def call generate_report additional_actions end private attr_reader :data, :generator def generate_report generator.new(data).generate end def additional_actions ... end end
Chúng ta đã cho phép thêm trực tiếp class generator mong muốn là tham số trong hàm constructor. Bây giờ, high-level class của chúng ta sẽ mang một mục đích chung và sẽ có thể sử dụng cho tất cả các loại generator.
Giải pháp được cung cấp linh hoạt hơn và dễ dàng viết unit test hơn.
Chúng ta có thể dễ bị nhầm lẫn giữa Dependency Inversion Principle và Dependency Injection, nhưng còn có thuật ngữ thứ 3 cũng rất dễ gây hiểu lầm. Nó là Inversion of Control. Chi tiết mọi người có thể tìm hiểu tại bài article của Martin Fowler: DIP in the Wild Tóm tắt lại thì là: "DI là về hệ thống dây điện, IoC là về phương hướng, và DIP là về hình dạng. - Martin Fowler" Khó hiểu vl