Dependency Inversion Principle - DIP là một trong bộ nguyên lý thiết kế SOLID.
Ở phần 1, chúng ta đã bàn về nội dung của DIP, sau đó là thói quen ứng dụng DIP thường gặp. Ngoài ra, ta còn đi được một quãng trên con đường "hiểu bản chất" DIP, bao gồm: phụ thuộc là gì, thế nào là cấp cao, cấp thấp.
Bây giờ, để tới đích, ta cần bước (xuyên) qua: Thế nào là ổn định? Trừu tượng và chi tiết là gì? Và, một ứng dụng quan trọng của DIP.
Ổn định (stable) và Bất ổn (instable)
Từ "ổn định" cũng như "bất ổn" không được nhắc tới trực tiếp trong nội dung của DIP nhưng lại được ngầm định chặt chẽ. Bạn đã thấy những thứ thế nào được coi là cấp cao, những thứ thế nào được coi là cấp thấp rồi, và những thứ cấp thấp thì phụ thuộc vào cấp cao. Quay lại ví dụ về các lớp trong ứng dụng, tôi biểu diễn mũi tên phụ thuộc theo cách khác bạn sẽ thấy tường minh hơn:

Bạn thấy điều gì trong hình 1 bên trên? Business Layer có cấp cao nhất, và nó được đặt trên đỉnh của cây phụ thuộc, còn hai layer còn lại ở bên dưới do chúng ở cấp thấp hơn. Vậy bạn nghĩ xem layer nào ổn định hơn?
Suy nghĩ chợt qua:
- Business Layer sẽ là nơi ít ổn định nhất vì nó là nơi cần phải điều chỉnh theo nghiệp vụ.
- Và nữa, Presentation Layer và Data Access Layer vì phụ thuộc vào Business Layer nên chúng ổn định hơn Business Layer.
Nhưng tôi sẽ "tâm sự" với bạn như những người đàn ông có trách nhiệm nhé: Ngày mới ra trường và đi làm, tôi dễ chuyển việc lắm. Vì khi đó, tôi còn trẻ, chưa có vướng bận gia định (bồi hồi chút) nên tôi chả phải lo gì, chỉ cần báo nghỉ việc, bàn giao công việc, và tới nơi mới. Nhưng giờ đây, khi đã có vợ và hai đứa nhỏ, tôi không thể tự do làm như thế nữa. Mỗi khi có ý định chuyển việc, tôi sẽ phải suy nghĩ, đắn đo, cân nhắc về môi trường, định hướng, thu nhập. Quan trọng nhất là làm thế nào để không ảnh hưởng tới gia đình hoặc làm cho gia đình mình tốt hơn.
Qua tâm sự trên, bạn đã thấy chưa? Khi mình có nhiều trách nhiệm hơn, mình sẽ khó thay đổi hơn.
Quay lại hình 1, Presentation Layer phụ thuộc vào Business Layer nên Business Layer thêm một lý do để không thay đổi, Data Access Layer phụ thuộc vào Business Layer nên Business Layer có thêm một lý do nữa để không thay đổi. Bởi vì hoạt động của Business Layer có thể ảnh hưởng tới kết quả của Presentation Layer và Data Access Layer.
Ngược lại, Presentation Layer và Data Access Layer dễ thay đổi hơn Business Layer vì chúng không có bất cứ thành phần nào phụ thuộc vào.
Tóm lại, Business Layer ổn định nhất, còn Presentation Layer và Data Access Layer ít ổn định hơn.
Một cách chắc chắn để tạo ra một thành phần khó thay đổi là tạo ra nhiều thành phần khác phụ thuộc vào nó. Bởi vì, khi mình thay đổi thành phần đó, mình phải thu xếp lại mọi thứ ở mọi thành phần mà nó được phụ thuộc vào, đó là khối lượng công việc mà mình phải làm để thay đổi. Giống như khi tôi muốn chuyển việc, tôi phải xem xét rất nhiều khía cạnh để đảm bảo không chỉ cho cá nhân tôi mà còn cho gia đình tôi.
Hãy tham chiếu vào thực tế,
- Ở Presentation Layer, bạn có thể bổ sung logic validate dữ liệu sơ bộ, cập nhật các logic biến đổi data đầu vào cho tầng Business Layer một cách thoải mái mà không làm ảnh hưởng gì tới Business Layer. Khi đó, bạn không cần compile lại Business Layer.
- Còn ở Data Access Layer, bạn có thể thay đổi cơ chế giao tiếp với database từ Hibernate sang JDBC, hoặc đổi database. Không vấn đề gì với Business Layer.
Tới đây, tôi lại đoán nhé: Bạn sẽ nghĩ Business Layer ổn định thì sao mà áp dụng được các nguyên lý như Open/Closed Principle (OCP), tức là làm thế nào để mở rộng, thay đổi logic nghiệp vụ?
Hãy cùng đi tới phần tiếp theo.
Trừu tượng và Chi tiết
Trừu tượng
Tiếp tục phần trên, ta thấy, đỉnh của sơ đồ phụ thuộc là các class ổn định nhất. Và chúng chính là nơi thể hiện chính sách ở cấp độ cao nhất trong phần mềm của ta. Những chính sách này là linh hồn của ứng dụng, thể hiện những quyết định thiết kế kiến trúc then chốt, và do đó, ta không nên thay đổi chúng quá thường xuyên.
Nhưng như vấn đề cuối phần trước đặt ra: các chính sách cấp cao đặt tại những thành phần ổn định thì source code của chúng sẽ trở nên khó thay đổi. Điều này làm cho phần mềm của ta thiếu linh hoạt trước những yêu cầu nghiệp vụ mới.
Làm thế nào để áp dụng được OCP? Câu trả lời: Abstraction - Trừu tượng.
Những thành phần cấp cao ổn định nên là trừu tượng để tính ổn định của nó không ngăn cản khả năng mở rộng của nó.
Điều này thể hiện rõ ràng trong cách code thường thấy của chúng ta:
- Ta khai báo
ShipmentService
interface đểShipmentController
sử dụng.ShipmentService
interface là một trừu tượng ổn định, nó có thể dễ dàng được mở rộng bằng cách thay thế một class implementation khác. - Trong ShipmentServiceImpl, ta khai báo ShipmentRepository interface là một trừu tượng ổn định. Logic nghiệp vụ cần lấy Shipment và thao tác nghiệp vụ, sau đó save xuống, tất cả đều qua ShipmentRepository. Nhưng việc lưu vào đâu, database hay gọi sang 1 web service qua API là do ShipmentRepositoryImpl xác định.
Hình sau biểu diễn điều này:

ShipmentService
và ShipmentRepository
là những trừu tường ổn định của ứng dụngNgược lại với trừu tượng là Chi tiết - Concrete.
Chi tiết
Chi tiết là những thứ mà ta cố tình tạo ra để nó thay đổi.
ShipmentController, JPAServiceRepository, hay thậm chí cả ShipmentServiceImpl (ta có thể sử dụng một abstract class để ShipmentServiceImpl kế thừa, giúp nghiệp vụ ShipmentService ổn định hơn, tránh những thay đổi trực tiếp) đều là những class sinh ra để thay đổi.
Quay lại DIP
Qua những phân tích trên, ta thấy rằng nguyên lý DIP phát biểu hoàn hợp lý (tất nhiên rồi) và các ví dụ của ta cũng thể hiện sự tuân thủ khá tốt.
Chợt bạn nhận ra vấn đề khi tưởng mọi việc đã "êm xuôi": Nếu ứng dụng của ta, cụ thể hơn là phần logic nghiệp vụ, chỉ phụ thuộc vào những gì là cấp cao, là ổn định, là trừu tượng, thì lấy đâu ra những thứ thực thi cụ thể? Ta rất không nên viết ra những thành phần mà trong đó toàn sử dụng trừu tượng, thực ra, việc này cũng là rất khó.
Tức là đảm bảo DIP rất khó?
Không hẳn vậy, bạn nhớ Shipment, Driver chứ? Chúng là những Domain Entities, ở cấp cao nhất của ứng dụng và chúng ổn định, và ta phải code logic xoay quanh chúng.
Hay bạn thử xem lại vào chương trình của mình xem nào. Trong đó có String, Integer, int, Date,... Đó đều là những concrete class, đều là những thứ cực kỳ chi tiết. Và từ ngày làm quen với Java tới giờ, bạn vẫn thường xuyên sử dụng nó mà không bao giờ mảy may lo lắng về việc nó sẽ thay đổi và ảnh hưởng tới chương trình của bạn.
Vậy, ta chấp nhận phụ thuộc vào những thứ cụ thể mà những thứ đó không thay đổi.
Những gì còn sót lại?
Nghĩ lại từ đầu tới giờ, ta đã đề cập tới các module cấp cao, cấp thấp, ổn định, trừu tượng và cả những thứ chi tiết nhưng ổn định. Thế ta còn lại gì?
Chỉ còn:
Những thứ cụ thể nhưng được ta tạo ra để ta có thể tích cực thay đổi và chúng đang được thay đổi thường xuyên.
Những thứ này, nên phụ thuộc vào những thứ còn lại: cấp cao hơn, trừu tượng hơn, và ổn định hơn.
(Do nội dung đã tương đối dài và nếu tiếp tục có thể gây khó hiểu nên tôi ngắt sang phần 3 ở đây)