Như mọi khi, Nó phụ thuộc ™. Câu trả lời phụ thuộc vào vấn đề người ta đang cố gắng giải quyết. Trong câu trả lời này, tôi sẽ cố gắng giải quyết một số lực thúc đẩy phổ biến:
Ưu tiên cơ sở mã nhỏ hơn
Nếu bạn có 4.000 dòng mã cấu hình Spring, tôi cho rằng cơ sở mã có hàng ngàn lớp.
Đây không phải là vấn đề mà bạn có thể giải quyết sau thực tế, nhưng theo nguyên tắc thông thường, tôi có xu hướng thích các ứng dụng nhỏ hơn, với các cơ sở mã nhỏ hơn. Ví dụ, nếu bạn tham gia vào Thiết kế hướng tên miền , bạn có thể tạo một cơ sở mã cho mỗi bối cảnh bị ràng buộc.
Tôi dựa trên lời khuyên này về kinh nghiệm hạn chế của mình, vì tôi đã viết mã ngành nghề kinh doanh dựa trên web cho phần lớn sự nghiệp của mình. Tôi có thể tưởng tượng rằng nếu bạn đang phát triển một ứng dụng máy tính để bàn, hoặc một hệ thống nhúng, hoặc hệ thống nhúng khác, thì mọi thứ sẽ khó tách rời hơn.
Mặc dù tôi nhận ra rằng lời khuyên đầu tiên này là dễ thực tế nhất, tôi cũng tin rằng đó là điều quan trọng nhất, và đó là lý do tại sao tôi đưa nó vào. Độ phức tạp của mã thay đổi phi tuyến tính (có thể theo cấp số nhân) với kích thước của cơ sở mã.
Ưu đãi Pure DI
Trong khi tôi vẫn nhận ra rằng câu hỏi này đưa ra một tình huống hiện có, tôi khuyên dùng Pure DI . Không sử dụng DI Container, nhưng nếu có, ít nhất hãy sử dụng nó để triển khai thành phần dựa trên quy ước .
Tôi không có bất kỳ trải nghiệm thực tế nào với Spring, nhưng tôi giả sử rằng bằng tệp cấu hình , một tệp XML được ngụ ý.
Cấu hình các phụ thuộc bằng XML là điều tồi tệ nhất của cả hai thế giới. Đầu tiên, bạn mất an toàn kiểu thời gian biên dịch, nhưng bạn không đạt được gì. Một tệp cấu hình XML có thể dễ dàng lớn như mã mà nó cố gắng thay thế.
So với vấn đề mà nó cố gắng giải quyết, các tệp cấu hình tiêm phụ thuộc chiếm sai vị trí trên đồng hồ phức tạp cấu hình .
Các trường hợp cho tiêm phụ thuộc hạt thô
Tôi có thể làm một trường hợp cho tiêm phụ thuộc hạt thô. Tôi cũng có thể tạo ra một trường hợp cho tiêm phụ thuộc hạt mịn (xem phần tiếp theo).
Nếu bạn chỉ tiêm một vài phụ thuộc 'trung tâm', thì hầu hết các lớp có thể trông như thế này:
public class Foo
{
private readonly Bar bar;
public Foo()
{
this.bar = new Bar();
}
// Members go here...
}
Điều này vẫn phù hợp với thành phần đối tượng ưu tiên của Mẫu thiết kế so với kế thừa lớp , bởi vì Foo
sáng tác Bar
. Từ góc độ bảo trì, điều này vẫn có thể được coi là có thể duy trì được, bởi vì nếu bạn cần thay đổi thành phần, bạn chỉ cần chỉnh sửa mã nguồn cho Foo
.
Điều này hầu như không thể duy trì ít hơn so với tiêm phụ thuộc. Trên thực tế, tôi muốn nói rằng việc chỉnh sửa trực tiếp lớp sử dụng sẽ dễ dàng hơn Bar
, thay vì phải tuân theo quy tắc cố định bằng cách tiêm phụ thuộc.
Trong ấn bản đầu tiên của cuốn sách về Dependency Injection , tôi đã phân biệt giữa các phụ thuộc dễ bay hơi và ổn định.
Phụ thuộc dễ bay hơi là những phụ thuộc mà bạn nên xem xét tiêm. Chúng bao gồm
- Các phụ thuộc phải được cấu hình lại sau khi biên dịch
- Sự phụ thuộc được phát triển song song bởi một nhóm khác
- Phụ thuộc với hành vi không xác định hoặc hành vi có tác dụng phụ
Mặt khác, các phụ thuộc ổn định là các phụ thuộc hoạt động theo cách được xác định rõ. Theo một nghĩa nào đó, bạn có thể lập luận rằng sự khác biệt này làm cho trường hợp tiêm phụ thuộc hạt thô, mặc dù tôi phải thừa nhận rằng tôi đã không hoàn toàn nhận ra điều đó khi tôi viết cuốn sách.
Tuy nhiên, từ góc độ thử nghiệm, điều này làm cho thử nghiệm đơn vị khó hơn. Bạn không còn có thể kiểm tra đơn vị Foo
độc lập với Bar
. Như JB Rainsberger giải thích , các bài kiểm tra tích hợp phải chịu một sự bùng nổ phức tạp. Theo nghĩa đen, bạn sẽ phải viết hàng chục ngàn trường hợp kiểm tra nếu bạn muốn bao quát tất cả các đường dẫn thông qua tích hợp 4-5 lớp.
Đối số thường là, nhiệm vụ của bạn không phải là lập trình một lớp. Nhiệm vụ của bạn là phát triển một hệ thống giải quyết một số vấn đề cụ thể. Đây là động lực đằng sau sự phát triển dựa trên hành vi (BDD).
Một quan điểm khác về điều này được trình bày bởi DHH, người tuyên bố rằng TDD dẫn đến thiệt hại thiết kế do thử nghiệm gây ra . Ông cũng ủng hộ thử nghiệm tích hợp hạt thô.
Nếu bạn có quan điểm này về phát triển phần mềm, thì tiêm phụ thuộc hạt thô có ý nghĩa.
Các trường hợp cho tiêm phụ thuộc hạt mịn
Mặt khác, tiêm phụ thuộc hạt mịn, có thể được mô tả là tiêm tất cả mọi thứ!
Mối quan tâm chính của tôi liên quan đến việc tiêm phụ thuộc hạt thô là những lời chỉ trích được thể hiện bởi JB Rainsberger. Bạn không thể bao gồm tất cả các đường dẫn mã bằng các kiểm tra tích hợp, bởi vì bạn cần viết đúng hàng ngàn hoặc hàng chục nghìn trường hợp kiểm thử để bao quát tất cả các đường dẫn mã.
Những người đề xuất của BDD sẽ phản bác lại lập luận rằng bạn không cần phải bao gồm tất cả các đường dẫn mã bằng các bài kiểm tra. Bạn chỉ cần bao gồm những người sản xuất giá trị kinh doanh.
Tuy nhiên, theo kinh nghiệm của tôi, tất cả các đường dẫn mã 'kỳ lạ' cũng sẽ thực thi trong triển khai khối lượng lớn và nếu không được kiểm tra, nhiều trong số đó sẽ có lỗi và gây ra ngoại lệ trong thời gian chạy (thường là ngoại lệ tham chiếu null).
Điều này đã khiến tôi ủng hộ việc tiêm phụ thuộc hạt mịn, bởi vì nó cho phép tôi kiểm tra các bất biến của tất cả các đối tượng trong sự cô lập.
Ưu tiên lập trình chức năng
Trong khi tôi nghiêng về tiêm phụ thuộc hạt mịn, tôi đã chuyển trọng tâm của mình sang lập trình chức năng, trong số các lý do khác vì về bản chất nó có thể kiểm chứng được .
Bạn càng di chuyển về phía mã RẮN, nó càng trở nên có chức năng hơn . Sớm hay muộn, bạn cũng có thể đi sâu. Kiến trúc chức năng là kiến trúc Cổng và Bộ điều hợp và tiêm phụ thuộc cũng là một nỗ lực và Cổng và Bộ điều hợp . Tuy nhiên, sự khác biệt là một ngôn ngữ như Haskell thi hành kiến trúc đó thông qua hệ thống loại của nó.
Ưu tiên chương trình chức năng gõ tĩnh
Tại thời điểm này, về cơ bản, tôi đã từ bỏ việc lập trình hướng đối tượng (OOP), mặc dù nhiều vấn đề của OOP thực chất được kết hợp với các ngôn ngữ chính như Java và C # nhiều hơn chính khái niệm này.
Vấn đề với các ngôn ngữ OOP chính là gần như không thể tránh được vấn đề nổ tổ hợp, chưa được kiểm chứng, dẫn đến các ngoại lệ trong thời gian chạy. Mặt khác, các ngôn ngữ được nhập tĩnh như Haskell và F #, cho phép bạn mã hóa nhiều điểm quyết định trong hệ thống loại. Điều này có nghĩa là thay vì phải viết hàng ngàn bài kiểm tra, trình biên dịch sẽ chỉ cho bạn biết liệu bạn đã xử lý tất cả các đường dẫn mã có thể chưa (ở một mức độ nào đó, đó không phải là viên đạn bạc).
Ngoài ra, tiêm phụ thuộc không phải là chức năng . Lập trình chức năng thực sự phải từ chối toàn bộ khái niệm phụ thuộc . Kết quả là mã đơn giản hơn.
Tóm lược
Nếu bị buộc phải làm việc với C #, tôi thích tiêm phụ thuộc chi tiết hơn vì nó cho phép tôi bao phủ toàn bộ cơ sở mã với số lượng các trường hợp thử nghiệm có thể quản lý được.
Cuối cùng, động lực của tôi là phản hồi nhanh chóng. Tuy nhiên, kiểm tra đơn vị không phải là cách duy nhất để có được phản hồi .