Thực hành container phụ thuộc vào tiêm / IoC khi viết khung


18

Tôi đã sử dụng nhiều bộ chứa IoC khác nhau (Castle.Windsor, Autofac, MEF, v.v.) cho .Net trong một số dự án. Tôi đã thấy họ có xu hướng thường xuyên bị lạm dụng và khuyến khích một số thực hành xấu.

Có bất kỳ thực tiễn được thiết lập nào cho việc sử dụng container IoC, đặc biệt là khi cung cấp nền tảng / khung không? Mục đích của tôi là một người viết khung là làm cho mã đơn giản và dễ sử dụng nhất có thể. Tôi muốn viết một dòng mã để xây dựng một đối tượng hơn mười hoặc thậm chí chỉ hai.

Ví dụ: một vài mã có mùi mà tôi đã nhận thấy và không có đề xuất tốt để:

  1. Số lượng lớn các tham số (> 5) cho các nhà xây dựng. Tạo các dịch vụ có xu hướng phức tạp; tất cả các phụ thuộc được đưa vào thông qua hàm tạo - mặc dù thực tế là các thành phần hiếm khi là tùy chọn (ngoại trừ có thể trong thử nghiệm).

  2. Thiếu các lớp học tư nhân và nội bộ; đây có thể là một giới hạn cụ thể của việc sử dụng C # và Silverlight, nhưng tôi quan tâm đến cách giải quyết nó. Thật khó để nói giao diện khung là gì nếu tất cả các lớp đều công khai; nó cho phép tôi truy cập vào những phần riêng tư mà có lẽ tôi không nên chạm vào.

  3. Khớp nối vòng đời đối tượng với bộ chứa IoC. Thường rất khó để tự xây dựng các phụ thuộc cần thiết để tạo đối tượng. Vòng đời đối tượng quá thường xuyên được quản lý bởi khung IoC. Tôi đã thấy các dự án mà hầu hết các lớp được đăng ký là Singletons. Bạn thiếu kiểm soát rõ ràng và cũng bị buộc phải quản lý nội bộ (liên quan đến điểm trên, tất cả các lớp đều công khai và bạn phải tiêm chúng).

Ví dụ, khung .Net có nhiều phương thức tĩnh. chẳng hạn như DateTime.UtcNow. Nhiều lần tôi đã thấy cái này được bọc và được tiêm như một tham số xây dựng.

Tùy thuộc vào việc thực hiện cụ thể làm cho mã của tôi khó kiểm tra. Việc tiêm một phụ thuộc làm cho mã của tôi khó sử dụng - đặc biệt nếu lớp có nhiều tham số.

Làm cách nào để cung cấp cả giao diện có thể kiểm tra, cũng như giao diện dễ sử dụng? Các thực hành tốt nhất là gì?


1
Những thực hành xấu nào họ khuyến khích? Đối với các lớp học riêng, bạn không cần phải có rất nhiều trong số chúng nếu bạn có sự đóng gói tốt; đối với một điều, chúng khó kiểm tra hơn nhiều.
Aaronaught

Err, 1 và 2 là những thực hành xấu. Các nhà xây dựng lớn và không sử dụng đóng gói để thiết lập một giao diện tối thiểu? Ngoài ra, tôi đã nói riêng tư và nội bộ (sử dụng nội bộ hiển thị để kiểm tra). Tôi chưa bao giờ sử dụng một khung công tác buộc tôi phải sử dụng một bộ chứa IoC cụ thể.
Dave Hillier

2
Có thể là một dấu hiệu cần nhiều tham số cho các hàm tạo của lớp của bạn chính là mùi mã và DIP chỉ làm cho điều này rõ ràng hơn?
dreza

3
Sử dụng một bộ chứa IoC cụ thể trong một khung công tác thường không phải là thông lệ tốt. Sử dụng tiêm phụ thuộc một thực hành tốt. Bạn có biết sự khác biệt?
Aaronaught

1
@Aaronaught (trở về từ cõi chết). Sử dụng, "tiêm phụ thuộc là một thực hành tốt" là sai. Dependency Injection là một công cụ chỉ nên được sử dụng khi thích hợp. Sử dụng Dependency Injection mọi lúc là một thực hành tồi.
Dave Hillier

Câu trả lời:


27

Mẫu chống tiêm phụ thuộc hợp pháp duy nhất mà tôi biết là mẫu Định vị dịch vụ , là mẫu chống mẫu khi sử dụng khung DI cho nó.

Tất cả các mô hình chống được gọi là DI khác mà tôi đã nghe nói, ở đây hoặc ở nơi khác, chỉ là các trường hợp cụ thể hơn một chút về các mẫu chống OO / thiết kế phần mềm nói chung. Ví dụ:

  • Người xây dựng quá tiêm là vi phạm Nguyên tắc Trách nhiệm duy nhất . Quá nhiều đối số constructor chỉ ra quá nhiều phụ thuộc; quá nhiều phụ thuộc chỉ ra rằng lớp đang cố gắng làm quá nhiều. Thông thường lỗi này tương quan với các mùi mã khác, chẳng hạn như tên lớp dài bất thường hoặc mơ hồ ("người quản lý"). Các công cụ phân tích tĩnh có thể dễ dàng phát hiện khớp nối quá mức / liên tục.

  • Tiêm dữ liệu, trái ngược với hành vi, là một kiểu con của mô hình chống poltergeist , với 'geist trong trường hợp này là container. Nếu một lớp cần phải biết ngày giờ hiện tại, bạn không tiêm một DateTime, đó là dữ liệu; thay vào đó, bạn đưa ra một sự trừu tượng hóa trên đồng hồ hệ thống (tôi thường gọi là của tôi ISystemClock, mặc dù tôi nghĩ rằng có một cái chung hơn trong dự án SystemWrappers ). Điều này không chỉ đúng với DI; nó thực sự cần thiết cho khả năng kiểm tra, để bạn có thể kiểm tra các chức năng thay đổi theo thời gian mà không cần phải thực sự chờ đợi chúng.

  • Tuyên bố mỗi vòng đời như Singleton, đối với tôi, là một ví dụ hoàn hảo về lập trình sùng bái hàng hóa và ở mức độ thấp hơn là " đối tượng dừng " được đặt tên thông tục . Tôi đã thấy lạm dụng đơn lẻ nhiều hơn tôi quan tâm để nhớ, và rất ít trong số đó liên quan đến DI.

  • Một lỗi phổ biến khác là các loại giao diện dành riêng cho việc triển khai (có tên lạ như IOracleRepository) được thực hiện chỉ để có thể đăng ký nó trong vùng chứa. Điều này tự nó đã vi phạm Nguyên tắc đảo ngược phụ thuộc (chỉ vì nó là một giao diện, không có nghĩa là nó thực sự trừu tượng) và thường bao gồm sự phình to giao diện vi phạm Nguyên tắc phân chia giao diện .

  • Lỗi cuối cùng tôi thường thấy là "sự phụ thuộc tùy chọn", mà họ đã làm trong NerdDinner . Nói cách khác, có một hàm tạo chấp nhận phép nội xạ phụ thuộc, nhưng cũng có một hàm tạo khác sử dụng triển khai "mặc định". Điều này cũng vi phạm DIP và có xu hướng dẫn đến vi phạm LSP , theo thời gian, các nhà phát triển, bắt đầu đưa ra các giả định xung quanh việc triển khai mặc định và / hoặc bắt đầu các trường hợp mới sử dụng hàm tạo mặc định.

Như người xưa vẫn nói, bạn có thể viết FORTRAN bằng bất kỳ ngôn ngữ nào . Dependency Injection không phải là một viên đạn bạc mà sẽ ngăn chặn các nhà phát triển từ Screwing lên quản lý phụ thuộc của họ, nhưng nó không ngăn chặn một số lỗi phổ biến / chống mẫu:

...và như thế.

Rõ ràng là bạn không muốn thiết kế một khung để phụ thuộc vào việc triển khai bộ chứa IoC cụ thể , như Unity hoặc AutoFac. Đó là, một lần nữa, vi phạm DIP. Nhưng nếu bạn thấy mình thậm chí nghĩ về việc làm một cái gì đó như vậy, thì bạn chắc chắn đã mắc một số lỗi thiết kế, bởi vì Dependency Injection là một kỹ thuật quản lý phụ thuộc mục đích chung và không gắn liền với khái niệm về bộ chứa IoC.

Bất cứ điều gì có thể xây dựng một cây phụ thuộc; có thể đó là một container IoC, có thể đó là một thử nghiệm đơn vị với một loạt các giả, có thể đó là một trình điều khiển thử nghiệm cung cấp dữ liệu giả. Khung của bạn không nên quan tâm và hầu hết các khung tôi từng thấy không quan tâm, nhưng họ vẫn sử dụng rất nhiều phép tiêm phụ thuộc để có thể dễ dàng tích hợp vào bộ chứa IoC của người dùng cuối.

DI không phải là khoa học tên lửa. Chỉ cần cố gắng tránh newstaticngoại trừ khi có lý do thuyết phục để sử dụng chúng, chẳng hạn như phương thức tiện ích không có phụ thuộc bên ngoài hoặc lớp tiện ích không thể có bất kỳ mục đích nào ngoài khung (trình bao bọc xen kẽ và khóa từ điển là những ví dụ phổ biến về điều này).

Nhiều vấn đề với khung IoC xuất hiện khi các nhà phát triển lần đầu tiên học cách sử dụng chúng và thay vì thực sự thay đổi cách họ xử lý các phụ thuộc và trừu tượng để phù hợp với mô hình IoC, thay vào đó, hãy cố gắng điều khiển bộ chứa IoC để đáp ứng mong đợi của họ phong cách mã hóa cũ, thường sẽ liên quan đến khớp nối cao và độ gắn kết thấp. Mã xấu là mã xấu, cho dù nó sử dụng các kỹ thuật DI hay không.


Tôi có một vài câu hỏi. Đối với thư viện được phân phối trên NuGet, tôi không thể phụ thuộc vào hệ thống IoC do ứng dụng sử dụng thư viện chứ không phải bởi chính thư viện. Trong nhiều trường hợp, người dùng thư viện thực sự không quan tâm đến sự phụ thuộc bên trong của nó. Có vấn đề gì với việc có một hàm tạo mặc định khởi tạo các phụ thuộc bên trong và một hàm tạo dài hơn để thử nghiệm đơn vị và / hoặc triển khai tùy chỉnh không? Ngoài ra bạn nói để tránh "mới". Điều này có áp dụng cho những thứ như StringBuilder, DateTime và TimeSpan không?
Etienne Charland
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.