Trong khi câu trả lời của Mark là rất tốt cho các kịch bản web, các lỗ hổng quan trọng với việc áp dụng nó cho mọi kiến trúc (cụ thể là rich-client - tức là: WPF, WinForms, iOS, vv) là giả định rằng tất cả các thành phần cần thiết cho một hoạt động có thể/nên được tạo ra cùng một lúc.
Đối với máy chủ web, điều này có ý nghĩa vì mọi yêu cầu đều cực kỳ ngắn ngủi và bộ điều khiển ASP.NET MVC được tạo bởi khung bên dưới (không có mã người dùng) cho mọi yêu cầu đi kèm. có thể dễ dàng được sáng tác bởi một khuôn khổ DI, và có rất ít chi phí bảo trì để làm như vậy. Lưu ý rằng khung công tác web chịu trách nhiệm quản lý tuổi thọ của bộ điều khiển và cho mọi mục đích tuổi thọ của tất cả các phụ thuộc của nó (mà khung DI sẽ tạo/tiêm cho bạn khi tạo bộ điều khiển). Nó là hoàn toàn tốt đẹp mà các phụ thuộc sống trong suốt thời gian yêu cầu và mã người dùng của bạn không cần phải quản lý tuổi thọ của các thành phần và các thành phần phụ. Cũng lưu ý rằng các máy chủ web là không trạng thái trên các yêu cầu khác nhau (ngoại trừ trạng thái phiên, nhưng không liên quan đến cuộc thảo luận này) và bạn không bao giờ có nhiều cá thể điều khiển/bộ điều khiển con cần phải sống cùng một lúc để phục vụ một yêu cầu duy nhất.
Tuy nhiên, trong các ứng dụng khách hàng phong phú, điều này là không đúng. Nếu sử dụng kiến trúc MVC/MVVM (bạn nên dùng!) Phiên của người dùng dài và các bộ điều khiển tạo bộ điều khiển con/bộ điều khiển anh em khi người dùng điều hướng qua ứng dụng (xem lưu ý về MVVM ở phía dưới). Sự tương tự với thế giới web là mọi đầu vào của người dùng (nhấn nút, thao tác được thực hiện) trong ứng dụng khách phong phú tương đương với yêu cầu được khung web nhận được. Tuy nhiên, sự khác biệt lớn là bạn muốn các bộ điều khiển trong một ứng dụng khách hàng phong phú sống sót giữa các hoạt động (rất có thể người dùng thực hiện nhiều thao tác trên cùng một màn hình - được điều khiển bởi một bộ điều khiển cụ thể) và các bộ điều khiển phụ được tạo và hủy khi người dùng thực hiện các hành động khác nhau (suy nghĩ về điều khiển tab tạo ra tab nếu người dùng điều hướng đến nó hoặc một phần giao diện người dùng chỉ cần tải nếu người dùng thực hiện các tác vụ cụ thể trên màn hình).
Cả hai đặc điểm này có nghĩa là là mã người dùng cần quản lý tuổi thọ của bộ điều khiển/bộ điều khiển phụ và không được tạo phụ thuộc của bộ điều khiển trước (ví dụ: bộ điều khiển phụ, chế độ xem , các thành phần trình bày khác, v.v.). Nếu bạn sử dụng một khung công tác DI để thực hiện các trách nhiệm này, bạn sẽ không chỉ có nhiều mã mà nó không thuộc về (Xem: Constructor over-injection anti-pattern), nhưng bạn cũng sẽ cần phải chuyển một thùng chứa phụ thuộc trong hầu hết lớp trình bày của bạn các thành phần của bạn có thể sử dụng nó để tạo các thành phần con của chúng khi cần.
Tại sao mã người dùng của tôi có quyền truy cập vào vùng chứa DI?
1) Vùng chứa phụ thuộc chứa tham chiếu đến nhiều thành phần trong ứng dụng của bạn. Thông qua cậu bé xấu này xung quanh mọi thành phần cần tạo/quản lý thành phần phụ anoter là tương đương với việc sử dụng các hình cầu trong kiến trúc của bạn. Tồi tệ hơn bất kỳ thành phần phụ nào cũng có thể đăng ký thành phần mới vào vùng chứa để đủ nhanh nó cũng sẽ trở thành một lưu trữ toàn cầu. Các nhà phát triển sẽ ném các đối tượng vào thùng chứa chỉ để truyền dữ liệu giữa các thành phần (giữa các bộ điều khiển anh chị em hoặc giữa các hệ thống phân cấp bộ điều khiển sâu - tức là: một bộ điều khiển tổ tiên cần lấy dữ liệu từ bộ điều khiển ông bà). Lưu ý rằng trong thế giới web nơi vùng chứa không được chuyển xung quanh đến mã người dùng, điều này không bao giờ là vấn đề.
2) Vấn đề khác với các gói phụ thuộc so với dịch vụ định vị/nhà máy/instantiation instantiation đối tượng là giải quyết từ một container làm cho nó hoàn toàn mơ hồ cho dù bạn đang tạo một thành phần hoặc đơn giản là REUSING hiện có. Thay vào đó, nó được để lại một cấu hình tập trung (ví dụ: bootstrapper/Root Root) để tìm ra tuổi thọ của thành phần là gì. Trong một số trường hợp, điều này là ổn (ví dụ: bộ điều khiển web, nơi mà nó không phải là mã người dùng cần quản lý tuổi thọ của thành phần nhưng chính khung thời gian xử lý yêu cầu thời gian chạy). Tuy nhiên, điều này là cực kỳ có vấn đề khi thiết kế các thành phần của bạn CHỈ ĐỊNH cho dù đó là trách nhiệm của họ để quản lý một thành phần và tuổi thọ của nó (Ví dụ: Một ứng dụng điện thoại bật lên một bảng yêu cầu người dùng cung cấp một số thông tin. Khi người dùng nhập một số thông tin, trang tính sẽ bị từ chối, và điều khiển được trả lại cho bộ điều khiển ban đầu, điều này vẫn duy trì trạng thái từ những gì người dùng đã thực hiện trước đó). Nếu DI được sử dụng để giải quyết bảng điều khiển phụ nó mơ hồ những gì cuộc đời của nó nên được hoặc ai sẽ chịu trách nhiệm quản lý nó (bộ điều khiển khởi tạo). So sánh điều này với trách nhiệm rõ ràng được quyết định bởi việc sử dụng các cơ chế khác.
Kịch bản A:
// not sure whether I'm responsible for creating the thing or not
DependencyContainer.GimmeA<Thing>()
Kịch bản B:
// responsibility is clear that this component is responsible for creation
Factory.CreateMeA<Thing>()
// or simply
new Thing()
Kịch bản C:
// responsibility is clear that this component is not responsible for creation, but rather only consumption
ServiceLocator.GetMeTheExisting<Thing>()
// or simply
ServiceLocator.Thing
Như bạn thấy DI làm cho nó rõ ràng ai là chịu trách nhiệm về việc quản lý vòng đời của tiểu hợp phần.
LƯU Ý: Về mặt kỹ thuật nói nhiều khuôn khổ DI có một số cách để tạo thành phần uể oải (Xem: How not to do dependency injection - the static or singleton container) mà là tốt hơn rất nhiều so với đi qua container xung quanh, nhưng bạn vẫn đang phải trả chi phí thay đổi mã của bạn để truyền xung quanh các chức năng tạo ở mọi nơi, bạn thiếu hỗ trợ cấp 1 để chuyển các thông số hàm dựng hợp lệ trong khi tạo, và vào cuối ngày bạn vẫn đang sử dụng cơ chế chuyển hướng không cần thiết ở những nơi mà lợi ích duy nhất là để đạt được khả năng kiểm tra, có thể đạt được theo những cách tốt hơn, đơn giản hơn (xem bên dưới).
Điều này có nghĩa là gì?
Điều đó có nghĩa là DI thích hợp cho một số trường hợp nhất định và không thích hợp cho người khác. Trong các ứng dụng khách hàng phong phú, nó xảy ra để mang lại rất nhiều nhược điểm của DI với rất ít trong số các upsides. Ứng dụng của bạn càng phức tạp càng nhiều thì chi phí bảo trì càng lớn. Nó cũng mang tiềm năng nghiêm trọng cho việc sử dụng sai, tùy thuộc vào mức độ liên lạc của nhóm và quy trình xem xét mã của bạn chặt chẽ, có thể ở bất kỳ đâu từ một vấn đề không đến chi phí nợ công nghệ cao. Có một huyền thoại xảy ra xung quanh rằng các nhà cung cấp dịch vụ hoặc nhà máy cũ hoặc tốt cũ là cơ chế xấu và lỗi thời đơn giản vì chúng có thể không phải là cơ chế tối ưu trong thế giới ứng dụng web, nơi mà có rất nhiều người chơi. Chúng ta không nên over- tổng quát những bài học này cho tất cả các kịch bản và xem mọi thứ như móng tay chỉ vì chúng ta đã học được cách sử dụng một cái búa cụ thể.
Đề xuất của tôi ĐỐI VỚI ỨNG DỤNG RICH-CLIENT là sử dụng cơ chế tối thiểu đáp ứng các yêu cầu cho từng thành phần trong tầm tay. 80% thời gian này nên được kích hoạt trực tiếp. Các định vị dịch vụ có thể được sử dụng để chứa các thành phần lớp kinh doanh chính của bạn (ví dụ: các dịch vụ ứng dụng thường đơn lẻ), và tất nhiên các nhà máy và thậm chí cả mẫu Singleton cũng có vị trí của chúng. Không có gì để nói rằng bạn không thể sử dụng khung DI ẩn đằng sau định vị dịch vụ của bạn để tạo ra các phụ thuộc lớp kinh doanh của bạn và mọi thứ chúng phụ thuộc vào một lần - nếu điều đó làm cho cuộc sống của bạn dễ dàng hơn trong lớp đó, và lớp đó không không hiển thị tải chậm mà các lớp trình bày phong phú của khách hàng thực hiện một cách đầy đủ là. Chỉ cần đảm bảo bảo vệ mã người dùng của bạn khỏi quyền truy cập vào vùng chứa đó để bạn có thể ngăn chặn sự lộn xộn đi qua vùng chứa DI xung quanh có thể tạo.
Điều gì về khả năng kiểm tra?
Khả năng kiểm tra hoàn toàn có thể đạt được mà không có khung DI. Tôi khuyên bạn nên sử dụng một khung đánh chặn như UnitBox (miễn phí) hoặc TypeMock (đắt tiền). Các khung công tác này cung cấp cho bạn các công cụ cần thiết để giải quyết vấn đề (cách bạn giả lập các cuộc gọi tức thời và tĩnh trong C#) và không yêu cầu bạn thay đổi toàn bộ kiến trúc của mình. đã đi vào thế giới .NET/Java). Sẽ khôn ngoan hơn khi tìm ra giải pháp cho vấn đề ở bàn tay và sử dụng các cơ chế ngôn ngữ tự nhiên và các mẫu tối ưu cho thành phần cơ bản sau đó cố gắng phù hợp với mọi chốt vuông vào lỗ DI tròn. Một khi bạn bắt đầu sử dụng những cơ chế đơn giản hơn, cụ thể hơn, bạn sẽ nhận thấy có rất ít nhu cầu DI trong codebase của bạn nếu có.
LƯU Ý: Đối với kiến trúc MVVM
Trong kiến trúc MVVM cơ bản xem mô hình có hiệu quả đảm nhận trách nhiệm quản lý, vì vậy cho tất cả mục đích xem xét 'điều khiển' từ ngữ ở trên để áp dụng cho 'xem -mô hình'. Cơ bản MVVM hoạt động tốt cho các ứng dụng nhỏ nhưng khi sự phức tạp của ứng dụng phát triển, bạn có thể muốn để sử dụng phương pháp tiếp cận MVCVM. Các kiểu xem trở thành các DTO câm nhất thành tạo điều kiện cho ràng buộc dữ liệu với chế độ xem trong khi tương tác với lớp kinh doanh và giữa các nhóm mô hình xem đại diện cho màn hình/phụ được đóng gói thành các thành phần điều khiển/bộ điều khiển phụ rõ ràng . Trong cả hai kiến trúc, trách nhiệm của các bộ điều khiển tồn tại và trưng bày các đặc điểm tương tự được thảo luận ở trên.
Câu hỏi liên quan đến đoạn cuối cùng của bạn. Bạn có bất kỳ liên kết nào đến các ví dụ về loại thiết kế này không? –
Đó sẽ chỉ là DI cơ bản như Constructor Injection và như vậy ... –
Re đoạn cuối- điều này có nghĩa là bạn không sử dụng IOC trong các bài kiểm tra? Chỉ cần làm rõ. Oh, nhưng nếu mã bạn đang thử nghiệm cần nó để xây dựng chính nó? – Greg