9.1 Đảo ngược phụ thuộc

Hãy nhớ rằng, chúng tôi đã từng nói rằng trong ứng dụng máy chủ, bạn không thể chỉ tạo luồng thông qua new Thread().start()? Chỉ vùng chứa mới tạo chủ đề. Bây giờ chúng tôi sẽ phát triển ý tưởng này hơn nữa.

Tất cả các đối tượng cũng chỉ nên được tạo bởi vùng chứa . Tất nhiên, chúng tôi không nói về tất cả các đối tượng, mà là về cái gọi là đối tượng kinh doanh. Chúng cũng thường được gọi là thùng. Nền tảng của cách tiếp cận này phát triển từ nguyên tắc thứ năm của SOLID, đòi hỏi phải loại bỏ các lớp và chuyển sang giao diện:

  • Các mô-đun cấp cao nhất không nên phụ thuộc vào các mô-đun cấp thấp hơn. Cả hai cái đó và những cái khác nên phụ thuộc vào sự trừu tượng.
  • Trừu tượng không nên phụ thuộc vào chi tiết. Việc thực hiện phải phụ thuộc vào sự trừu tượng.

Các mô-đun không được chứa các tham chiếu đến các triển khai cụ thể và tất cả các phụ thuộc và tương tác giữa chúng chỉ nên được xây dựng trên cơ sở trừu tượng hóa (nghĩa là các giao diện). Bản chất của quy tắc này có thể được viết bằng một cụm từ: tất cả các phụ thuộc phải ở dạng giao diện .

Mặc dù bản chất cơ bản và sự đơn giản rõ ràng của nó, quy tắc này bị vi phạm thường xuyên nhất. Cụ thể, mỗi khi chúng ta sử dụng toán tử mới trong mã của chương trình/mô-đun và tạo một đối tượng mới thuộc một loại cụ thể, do đó, thay vì phụ thuộc vào giao diện, sự phụ thuộc vào việc triển khai được hình thành.

Rõ ràng là không thể tránh được điều này và các đối tượng phải được tạo ở đâu đó. Tuy nhiên, ít nhất, bạn cần giảm thiểu số lượng nơi thực hiện việc này và lớp nào được chỉ định rõ ràng, cũng như bản địa hóa và cô lập những nơi đó để chúng không nằm rải rác trong mã chương trình.

Một giải pháp rất tốt là ý tưởng điên rồ về việc tập trung tạo các đối tượng mới trong các đối tượng và mô-đun chuyên biệt - nhà máy, bộ định vị dịch vụ, bộ chứa IoC.

Theo một nghĩa nào đó, một quyết định như vậy tuân theo Nguyên tắc lựa chọn duy nhất, trong đó nói: "Bất cứ khi nào một hệ thống phần mềm phải hỗ trợ nhiều lựa chọn thay thế, thì danh sách đầy đủ của chúng chỉ nên được biết bởi một mô-đun của hệ thống" .

Do đó, nếu trong tương lai cần thêm các tùy chọn mới (hoặc triển khai mới, như trong trường hợp tạo các đối tượng mới mà chúng tôi đang xem xét), thì chỉ cần cập nhật mô-đun chứa thông tin này và tất cả các mô-đun khác là đủ sẽ không bị ảnh hưởng và có thể tiếp tục công việc của họ như bình thường.

ví dụ 1

new ArrayList Thay vì viết một cái gì đó như , JDK sẽ List.new()cung cấp cho bạn cách triển khai chính xác của một lá: ArrayList, LinkedList hoặc thậm chí ConcurrentList.

Ví dụ: trình biên dịch thấy rằng có các cuộc gọi đến đối tượng từ các luồng khác nhau và đặt triển khai an toàn cho luồng ở đó. Hoặc chèn quá nhiều vào giữa sheet thì việc triển khai sẽ dựa vào LinkedList.

ví dụ 2

Ví dụ, điều này đã xảy ra với các loại. Lần cuối cùng bạn viết thuật toán sắp xếp để sắp xếp một bộ sưu tập là khi nào? Thay vào đó, bây giờ mọi người đều sử dụng phương thức Collections.sort()và các phần tử của bộ sưu tập phải hỗ trợ giao diện So sánh được (có thể so sánh được).

Nếu sort()bạn chuyển một tập hợp có ít hơn 10 phần tử cho phương thức, thì hoàn toàn có thể sắp xếp nó bằng sắp xếp bong bóng (Sắp xếp bong bóng) chứ không phải Quicksort.

ví dụ 3

Trình biên dịch đã xem cách bạn nối các chuỗi và sẽ thay thế mã của bạn bằng StringBuilder.append().

9.2 Nghịch đảo phụ thuộc trong thực tế

Bây giờ điều thú vị nhất: hãy nghĩ về cách chúng ta có thể kết hợp lý thuyết và thực hành. Làm cách nào để các mô-đun có thể tạo và nhận “các phần phụ thuộc” của chúng một cách chính xác và không vi phạm Đảo ngược phụ thuộc?

Để làm điều này, khi thiết kế một mô-đun, bạn phải tự quyết định:

  • module làm gì, thực hiện chức năng gì;
  • sau đó mô-đun cần từ môi trường của nó, tức là nó sẽ phải xử lý những đối tượng / mô-đun nào;
  • Và làm thế nào anh ta sẽ nhận được nó?

Để tuân thủ các nguyên tắc của Đảo ngược phụ thuộc, chắc chắn bạn cần phải quyết định các đối tượng bên ngoài mà mô-đun của bạn sử dụng và cách thức tham chiếu đến chúng.

Và đây là các tùy chọn sau:

  • mô-đun tự tạo đối tượng;
  • mô-đun lấy các đối tượng từ vùng chứa;
  • mô-đun không biết đối tượng đến từ đâu.

Vấn đề là để tạo một đối tượng, bạn cần gọi hàm tạo của một loại cụ thể và kết quả là mô-đun sẽ không phụ thuộc vào giao diện mà phụ thuộc vào cách triển khai cụ thể. Nhưng nếu chúng ta không muốn các đối tượng được tạo rõ ràng trong mã mô-đun, thì chúng ta có thể sử dụng mẫu Factory Method .

"Điểm mấu chốt là thay vì trực tiếp khởi tạo một đối tượng thông qua new, chúng tôi cung cấp cho lớp máy khách một số giao diện để tạo đối tượng. Vì giao diện như vậy luôn có thể được ghi đè với thiết kế phù hợp, nên chúng tôi có một số tính linh hoạt khi sử dụng các mô-đun cấp thấp trong các mô-đun cấp cao" .

Trong trường hợp cần tạo các nhóm hoặc họ của các đối tượng liên quan, một Abstract factory được sử dụng thay cho Factory Method .

9.3 Sử dụng Bộ định vị Dịch vụ

Mô-đun lấy các đối tượng cần thiết từ đối tượng đã có chúng. Giả định rằng hệ thống có một số kho lưu trữ đối tượng, trong đó các mô-đun có thể “đặt” đối tượng của chúng và “lấy” đối tượng từ kho lưu trữ.

Cách tiếp cận này được thực hiện bởi mẫu Định vị dịch vụ , ý tưởng chính là chương trình có một đối tượng biết cách lấy tất cả các phụ thuộc (dịch vụ) có thể được yêu cầu.

Sự khác biệt chính so với các nhà máy là Service Locator không tạo các đối tượng mà thực tế đã chứa các đối tượng được khởi tạo (hoặc biết vị trí / cách lấy chúng và nếu nó tạo thì chỉ một lần ở lần gọi đầu tiên). Nhà máy tại mỗi cuộc gọi sẽ tạo một đối tượng mới mà bạn có toàn quyền sở hữu và bạn có thể làm bất cứ điều gì mình muốn với nó.

Quan trọng ! Bộ định vị dịch vụ tạo ra các tham chiếu đến cùng các đối tượng đã tồn tại . Do đó, bạn cần hết sức cẩn thận với các đối tượng do Bộ định vị dịch vụ cấp, vì người khác có thể sử dụng chúng cùng lúc với bạn.

Các đối tượng trong Bộ định vị dịch vụ có thể được thêm trực tiếp thông qua tệp cấu hình và thực sự theo bất kỳ cách nào thuận tiện cho người lập trình. Bản thân Service Locator có thể là một lớp tĩnh với một tập hợp các phương thức tĩnh, một singleton hoặc một giao diện và có thể được chuyển đến các lớp được yêu cầu thông qua một hàm tạo hoặc phương thức.

Bộ định vị dịch vụ đôi khi được gọi là chống mẫu và không được khuyến khích (vì nó tạo ra các kết nối ngầm và chỉ mang lại vẻ ngoài của một thiết kế tốt). Bạn có thể đọc thêm từ Mark Seaman:

9.4 Tiêm phụ thuộc

Mô-đun hoàn toàn không quan tâm đến các phụ thuộc "khai thác". Nó chỉ xác định những gì nó cần để hoạt động và tất cả các phụ thuộc cần thiết đều được cung cấp (giới thiệu) từ bên ngoài bởi người khác.

Đây là những gì được gọi là - Tiêm phụ thuộc . Thông thường, các phụ thuộc bắt buộc được truyền dưới dạng tham số hàm tạo (Constructor Injection) hoặc thông qua các phương thức lớp (Setter injection).

Cách tiếp cận này đảo ngược quá trình tạo các phụ thuộc - thay vì chính mô-đun, việc tạo các phụ thuộc được kiểm soát bởi một người nào đó từ bên ngoài. Mô-đun từ bộ phát chủ động của các đối tượng trở thành thụ động - không phải anh ta tạo ra, mà là những người khác tạo ra cho anh ta.

Sự thay đổi hướng này được gọi là Đảo ngược kiểm soát , hay Nguyên tắc Hollywood - "Đừng gọi cho chúng tôi, chúng tôi sẽ gọi cho bạn."

Đây là giải pháp linh hoạt nhất, mang lại cho các mô-đun quyền tự chủ cao nhất . Có thể nói rằng chỉ có nó mới thực hiện đầy đủ "Nguyên tắc chịu trách nhiệm duy nhất" - mô-đun phải hoàn toàn tập trung vào việc làm tốt công việc của mình và không phải lo lắng về bất kỳ điều gì khác.

Cung cấp cho mô-đun mọi thứ cần thiết cho công việc là một nhiệm vụ riêng biệt, nhiệm vụ này phải được xử lý bởi “chuyên gia” thích hợp (thường là một bộ chứa nhất định, bộ chứa IoC, chịu trách nhiệm quản lý các phụ thuộc và việc triển khai chúng).

Trên thực tế, mọi thứ ở đây giống như trong cuộc sống: trong một công ty được tổ chức tốt, các lập trình viên lập trình, bàn làm việc, máy tính và mọi thứ họ cần cho công việc đều do người quản lý văn phòng mua và cung cấp. Hoặc, nếu bạn sử dụng phép ẩn dụ của chương trình như một hàm tạo, thì mô-đun không nên nghĩ về dây, một người khác sẽ tham gia vào việc lắp ráp hàm tạo chứ không phải bản thân các bộ phận.

Sẽ không quá lời khi nói rằng việc sử dụng các giao diện để mô tả các quan hệ phụ thuộc giữa các mô-đun (Đảo ngược phụ thuộc) + việc tạo và đưa vào chính xác các quan hệ phụ thuộc này (chủ yếu là Tiêm phụ thuộc) là các kỹ thuật chính để tách .

Chúng đóng vai trò là nền tảng mà trên đó sự liên kết lỏng lẻo của mã, tính linh hoạt, khả năng chống lại các thay đổi, khả năng tái sử dụng và tất cả các kỹ thuật khác đều không có ý nghĩa gì nếu thiếu nó. Đây là nền tảng của khớp nối lỏng lẻo và kiến ​​trúc tốt.

Nguyên lý của Inversion of Control (cùng với Dependency Injection và Service Locator) được thảo luận chi tiết bởi Martin Fowler. Có bản dịch của cả hai bài viết của anh ấy: "Inversion of Control Containers and the Dependency Injection pattern""Inversion of Control" .