Design Pattern | Dependency Inversion Principle
Một vấn đề phổ biển mà bạn sẽ cần giải quyết khi thiết kế hệ thống của mình đó là sự phụ thuộc (dependency). Có một số câu hỏi sẽ xuất hiện khi nói đến chủ đề này, chẳng hạn như điểm phụ thuộc ở đâu ? Sự phụ thuộc phần mềm về cơ bản là khớp nối xác định mức độ phụ thuộc giữa các thành phần khác nhau của phần mềm. Nếu một phần trong hệ thống của bạn được ghép nối cao, thì mức độ mà chúng dựa vào nhau được coi là cao, trong khi mức độ phụ thuộc thấp có nghĩa là hệ thống của bạn có mức độ khớp nối thấp hơn ?
Sự phụ thuộc là một chủ đề quan trọng vì nó sẽ xác định mức độ bạn có thể dễ dàng thực hiện các thay đổi cho hệ thống của mình. Hãy nghĩ về nó theo cách này, nếu bạn phụ thuộc vào một loại thực phẩm cụ thể để sinh tồn, thì bạn sẽ không thể thay thế nó bằng thứ khác.
Trong một ví dụ thực tế, cơ thể chúng ta phụ thuộc vào nước để sinh tồn. Chúng ta không thể thay thế nước bằng bánh mì và hi vọng sẽ có được kết quả tương tự như một khi chúng ta uống nước. Bạn sẽ phải đối mặt với cùng một vấn đề trong phần mềm, cố gắng thay thế một lớp hoặc một tài nguyên cho một thứ gì khác không phải là dễ dàng thực hiện, hoặc đôi khi thậm chí là bất khả thi.
Điều này do hệ thống của bạn phụ thuộc vào một chức năng cụ thể chỉ có được cung cấp bởi lớp cụ thể đó. Để giải quyết vấn đề này, chúng ta sử dụng nguyên tắc thiết kế Dependency Inversion Principle, sẽ giúp cho bạn làm cho hệ thống của mình mạnh mẽ và linh hoạt hơn.
Nguyên tắc nêu rõ ràng là các module ở level cao nên phụ thuộc vào khái quát hóa cấp cao chứ không phụ thuộc vào chi tiết cấp thấp. Điều này có nghĩa là client class của bạn nên phụ thuộc vào một giao diện (interface) hoặc lớp trừu tượng (abstract) thay vì tham chiếu đến các tài nguyên cụ thể (concrete resource) và rằng các tài nguyên cụ thể của bạn nên có các hành vi của chúng được khái quát thành các giao diện hoặc lớp trừu tượng.
Ý tưởng là các interface và abstract được coi là tài nguyên cấp cao, trong khi một lớp cụ thể được coi là tài nguyên cấp thấp. Một interface hoặc abstract class định nghĩa một tập hợp các hành vi chung và các lớp cụ thể cung cấp việc thực hiện các hành vi này. Bằng cách này, các lớp client của bạn có thể độc lập với chức năng cấp thấp.
Tất cả các mẫu thiết kế đã đăng trước đây đều áp dụng nguyên tắc này, chúng ta hãy xem sự khác biệt giữa phụ thuộc cấp thấp và phụ thuộc cấp cao, để chứng minh Dependency Inversion Pinciple hoạt động như thế nào.
Trong một thiết kế phần mềm thông thường, thiết kế của chúng có thể trông như thế này:
Các hệ thống con của bạn phụ thuộc trực tiếp vào nhau, điều đó có nghĩa là lớp client trực tiếp tham chiếu một số lớp cụ thể trong hệ thống con Bankend SubSystem. Hình thức phụ thuộc này gọi là sự phụ thuộc mức thấp, bởi vì các lớp máy khách đang đặt tên và tạo tham chiếu đến một lớp cụ thể. Nhưng, điều gì sẽ xảy ra nếu bạn cần thay đổi references này trong tương lai ?
Ở đây, bạn có thể đặt tên trực tiếp và tham chiếu đến lớp cụ thể được gọi là quicksorting mà bạn sử dụng để sắp xếp được gửi đến hệ thống của bạn bởi người dùng. Vấn đề là nếu bạn triển khai một lớp sắp xếp khác được gọi là mergesort, bạn sẽ cần thực hiện các thay đổi quan trọng với hệ thống con của client.
Lưu ý bạn đã thay đổi loại lớp sắp xếp từ quicksort sang mergesort, và bạn cũng phải thay đổi phương thức gọi sắp xếp. Hãy tưởng tượng bạn sẽ cần bao nhiêu công việc mỗi khi bạn muốn thực hiện một thuật toán sắp xếp khác nhau để thay đổi hệ thống của bạn.
Không chỉ không thực tế, những loại thay đổi này có thể có tác dụng phụ không mong muốn nếu bạn bỏ lỡ bất kỳ tài liệu tham khảo cũ nào. Dependency Inversion giải quyết vấn đề này bằng cách khái quát hóa chức năng cấp thấp vào các giao diện hoặc các lớp trừu tượng, để khi triển khai thay thế được cung cấp, chúng có thể được sử dụng dễ dàng. Khi bạn sử dụng nguyên tắc này, kiến trúc tồng thể của hệ thống của bạn sẽ trông rất giống mẫu thiết kế mà chúng ta đã khám phá, bạn cần khái quát hóa mỗi hành vi trên một hệ thống con thành một giao diện. Và cuối cùng, các lớp client của bạn sẽ tạo các tham chiếu đến các giao diện thay vì trực tiếp đến các lớp cụ thể.
Như bạn có thể thấy, thay vì khai báo một lớp cụ thể, bạn có thể khai báo giao diện sắp xếp, sau đó bạn có thể xác định concrete của lớp sắp xếp mà bạn muốn hệ thống con client khởi tạo. Hơn nữa, vì bạn có thể khái quát hóa hành vi sắp xếp bằng một phương thức gọi là sort trong giao diện, bạn sẽ không cần phải thay đổi các lần gọi trong hệ thống con client. Vì lớp client của bạn phụ thuộc vào mức độ khái quát hóa cao hơn là lớp cụ thể, bạn có thể dễ dàng thay đổi tài nguyên nào mà khách hàng có thể sử dụng. Việc khái quát hóa mang đến cho bạn một mức độ gián tiếp bằng cách cho phép bạn gọi đến các hành vi trong một lớp cụ thể thông qua một giao diện. Điều này có nghĩa là lớp client phụ thuộc vào hành vi dự kiến, không phải hành vi cụ thể.
Không giống như các ngôn ngữ thủ tục, như C, trong đó các lệnh gọi thường được thực hiện trực tiếp trong thời điểm thực hiện chương trình, các hệ thống hướng đối tượng không cần phải thực hiện các lần gọi trực tiếp như vậy. Trong thực tế, các lần gọi phương thức trong một thiết kế hướng đối tượng nên được thực hiện bằng sử dụng một mức độ không xác định. Nguyên tắc này cho thấy rằng bạn nên chỉ định bằng cách viết các phụ thuộc vào các khái quát hơn là trực tiếp đến một lớp cụ thể.
Đó là một nguyên tắc cần tuân theo.
Sự phụ thuộc là một chủ đề quan trọng vì nó sẽ xác định mức độ bạn có thể dễ dàng thực hiện các thay đổi cho hệ thống của mình. Hãy nghĩ về nó theo cách này, nếu bạn phụ thuộc vào một loại thực phẩm cụ thể để sinh tồn, thì bạn sẽ không thể thay thế nó bằng thứ khác.
Trong một ví dụ thực tế, cơ thể chúng ta phụ thuộc vào nước để sinh tồn. Chúng ta không thể thay thế nước bằng bánh mì và hi vọng sẽ có được kết quả tương tự như một khi chúng ta uống nước. Bạn sẽ phải đối mặt với cùng một vấn đề trong phần mềm, cố gắng thay thế một lớp hoặc một tài nguyên cho một thứ gì khác không phải là dễ dàng thực hiện, hoặc đôi khi thậm chí là bất khả thi.
Điều này do hệ thống của bạn phụ thuộc vào một chức năng cụ thể chỉ có được cung cấp bởi lớp cụ thể đó. Để giải quyết vấn đề này, chúng ta sử dụng nguyên tắc thiết kế Dependency Inversion Principle, sẽ giúp cho bạn làm cho hệ thống của mình mạnh mẽ và linh hoạt hơn.
Nguyên tắc nêu rõ ràng là các module ở level cao nên phụ thuộc vào khái quát hóa cấp cao chứ không phụ thuộc vào chi tiết cấp thấp. Điều này có nghĩa là client class của bạn nên phụ thuộc vào một giao diện (interface) hoặc lớp trừu tượng (abstract) thay vì tham chiếu đến các tài nguyên cụ thể (concrete resource) và rằng các tài nguyên cụ thể của bạn nên có các hành vi của chúng được khái quát thành các giao diện hoặc lớp trừu tượng.
Ý tưởng là các interface và abstract được coi là tài nguyên cấp cao, trong khi một lớp cụ thể được coi là tài nguyên cấp thấp. Một interface hoặc abstract class định nghĩa một tập hợp các hành vi chung và các lớp cụ thể cung cấp việc thực hiện các hành vi này. Bằng cách này, các lớp client của bạn có thể độc lập với chức năng cấp thấp.
Tất cả các mẫu thiết kế đã đăng trước đây đều áp dụng nguyên tắc này, chúng ta hãy xem sự khác biệt giữa phụ thuộc cấp thấp và phụ thuộc cấp cao, để chứng minh Dependency Inversion Pinciple hoạt động như thế nào.
Trong một thiết kế phần mềm thông thường, thiết kế của chúng có thể trông như thế này:
Các hệ thống con của bạn phụ thuộc trực tiếp vào nhau, điều đó có nghĩa là lớp client trực tiếp tham chiếu một số lớp cụ thể trong hệ thống con Bankend SubSystem. Hình thức phụ thuộc này gọi là sự phụ thuộc mức thấp, bởi vì các lớp máy khách đang đặt tên và tạo tham chiếu đến một lớp cụ thể. Nhưng, điều gì sẽ xảy ra nếu bạn cần thay đổi references này trong tương lai ?
Ở đây, bạn có thể đặt tên trực tiếp và tham chiếu đến lớp cụ thể được gọi là quicksorting mà bạn sử dụng để sắp xếp được gửi đến hệ thống của bạn bởi người dùng. Vấn đề là nếu bạn triển khai một lớp sắp xếp khác được gọi là mergesort, bạn sẽ cần thực hiện các thay đổi quan trọng với hệ thống con của client.
Lưu ý bạn đã thay đổi loại lớp sắp xếp từ quicksort sang mergesort, và bạn cũng phải thay đổi phương thức gọi sắp xếp. Hãy tưởng tượng bạn sẽ cần bao nhiêu công việc mỗi khi bạn muốn thực hiện một thuật toán sắp xếp khác nhau để thay đổi hệ thống của bạn.
Không chỉ không thực tế, những loại thay đổi này có thể có tác dụng phụ không mong muốn nếu bạn bỏ lỡ bất kỳ tài liệu tham khảo cũ nào. Dependency Inversion giải quyết vấn đề này bằng cách khái quát hóa chức năng cấp thấp vào các giao diện hoặc các lớp trừu tượng, để khi triển khai thay thế được cung cấp, chúng có thể được sử dụng dễ dàng. Khi bạn sử dụng nguyên tắc này, kiến trúc tồng thể của hệ thống của bạn sẽ trông rất giống mẫu thiết kế mà chúng ta đã khám phá, bạn cần khái quát hóa mỗi hành vi trên một hệ thống con thành một giao diện. Và cuối cùng, các lớp client của bạn sẽ tạo các tham chiếu đến các giao diện thay vì trực tiếp đến các lớp cụ thể.
Như bạn có thể thấy, thay vì khai báo một lớp cụ thể, bạn có thể khai báo giao diện sắp xếp, sau đó bạn có thể xác định concrete của lớp sắp xếp mà bạn muốn hệ thống con client khởi tạo. Hơn nữa, vì bạn có thể khái quát hóa hành vi sắp xếp bằng một phương thức gọi là sort trong giao diện, bạn sẽ không cần phải thay đổi các lần gọi trong hệ thống con client. Vì lớp client của bạn phụ thuộc vào mức độ khái quát hóa cao hơn là lớp cụ thể, bạn có thể dễ dàng thay đổi tài nguyên nào mà khách hàng có thể sử dụng. Việc khái quát hóa mang đến cho bạn một mức độ gián tiếp bằng cách cho phép bạn gọi đến các hành vi trong một lớp cụ thể thông qua một giao diện. Điều này có nghĩa là lớp client phụ thuộc vào hành vi dự kiến, không phải hành vi cụ thể.
Không giống như các ngôn ngữ thủ tục, như C, trong đó các lệnh gọi thường được thực hiện trực tiếp trong thời điểm thực hiện chương trình, các hệ thống hướng đối tượng không cần phải thực hiện các lần gọi trực tiếp như vậy. Trong thực tế, các lần gọi phương thức trong một thiết kế hướng đối tượng nên được thực hiện bằng sử dụng một mức độ không xác định. Nguyên tắc này cho thấy rằng bạn nên chỉ định bằng cách viết các phụ thuộc vào các khái quát hơn là trực tiếp đến một lớp cụ thể.
Đó là một nguyên tắc cần tuân theo.
Nhận xét
Đăng nhận xét