Định nghĩa tốt nhất cho tiêm phụ thuộc là gì?


10

Mỗi khi có ai đó tiếp cận tôi và yêu cầu tôi xác định Dependency Injection theo cách khái niệm và giải thích những ưu và nhược điểm thực sự của việc sử dụng DI trong thiết kế phần mềm. Tôi thú nhận rằng tôi có một số khó khăn để giải thích các khái niệm về DI. Mỗi lần tôi cần nói với họ về lịch sử về nguyên tắc trách nhiệm duy nhất, thành phần so với thừa kế, v.v.

Bất cứ ai cũng có thể giúp tôi giải thích cách tốt nhất để mô tả DI cho các nhà phát triển?


2
Thách thức ở đây là có rất nhiều định nghĩa mâu thuẫn về DI. Tôi có lập trường "thuần DI": nếu tôi có một hàm dựa vào các tham số của nó để cung cấp tất cả trạng thái, dữ liệu, v.v. thì hàm đó đang sử dụng DI. Ở một thái cực khác, một số người sẽ lập luận rằng nếu không có khung DI, sẽ không có sự phụ thuộc vào nội dung (mặc dù họ đã sai tất nhiên;)). Vì vậy, trừ khi bạn có thể đưa ra một định nghĩa, bạn không thể bắt đầu giải thích nó là gì ...
David Arno

Vì vậy, theo tôi hiểu, đây không chỉ là vấn đề của tôi.
Tiago Sampaio



Tất cả đều bắt nguồn từ điều này: tiêm phụ thuộc là một kỹ thuật được sử dụng để đạt được nghịch đảo phụ thuộc; mọi thứ khác chỉ là những thứ bổ sung được xây dựng trên đó. Lưu ý rằng trong hai thuật ngữ này, từ "phụ thuộc" có nghĩa hơi khác nhau. Trong phần phụ thuộc, nó đề cập đến thành phần mà mã phụ thuộc vào. Trong nghịch đảo phụ thuộc, nó đề cập đến chính mối quan hệ (được định hướng) - mối quan hệ mà chúng ta muốn đảo ngược. Cái sau là mục tiêu, vì vậy ưu và nhược điểm chính là như nhau; cộng với một số mối quan tâm thêm liên quan đến thực hiện thực tế, như quản lý trọn đời đối tượng.
Filip Milovanović

Câu trả lời:


22

Dependency Injection là một cái tên kinh khủng (IMO) 1 cho một khái niệm khá đơn giản. Đây là một ví dụ:

  1. Bạn có một phương thức (hoặc lớp với các phương thức) thực hiện X (ví dụ: truy xuất dữ liệu từ cơ sở dữ liệu)
  2. Là một phần của việc thực hiện X, phương thức đã nói tạo và quản lý tài nguyên nội bộ (ví dụ a DbContext). Tài nguyên nội bộ này là cái được gọi là phụ thuộc
  3. Bạn loại bỏ việc tạo và quản lý tài nguyên (tức là DbContext) khỏi phương thức và biến nó thành trách nhiệm của người gọi để cung cấp tài nguyên này (dưới dạng tham số phương thức hoặc khi khởi tạo lớp)
  4. Bây giờ bạn đang làm tiêm phụ thuộc.


[1] : Tôi đến từ một nền tảng cấp thấp hơn và tôi đã mất nhiều tháng để ngồi xuống và học cách tiêm phụ thuộc vì cái tên ngụ ý nó sẽ là một thứ gì đó phức tạp hơn nhiều, như DLL Injection . Thực tế là Visual Studio (và chúng tôi nói chung là các nhà phát triển) đề cập đến các thư viện .NET (DLL hoặc hội đồng ) mà một dự án phụ thuộc vào vì phụ thuộc hoàn toàn không giúp ích gì. Thậm chí còn có một thứ như Dependency Walker (Dep.exe) .


[Chỉnh sửa] Tôi đã hình dung một số mã demo sẽ có ích cho một số người, vì vậy đây là một mã (trong C #).

Không tiêm phụ thuộc:

public class Repository : IDisposable
{
    protected DbContext Context { get; }

    public Repository()
    {
        Context = new DbContext("name=MyEntities");
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}

Người tiêu dùng của bạn sau đó sẽ làm một cái gì đó như:

using ( var repository = new Repository() )
{
    // work
}

Cùng một lớp được thực hiện với mô hình tiêm phụ thuộc sẽ như thế này:

public class RepositoryWithDI
{
    protected DbContext Context { get; }

    public RepositoryWithDI(DbContext context)
    {
        Context = context;
    }
}

Bây giờ, khả năng đáp ứng của người gọi để khởi tạo a DbContextvà chuyển (errm, chích ) nó đến lớp của bạn:

using ( var context = new DbContext("name=MyEntities") )
{
    var repository = new RepositoryWithDI(context);

    // work
}

3
Điều này nên được thêm vào Wikipedia.
Evorlor

2
Bây giờ đây là khả năng đáp ứng của người gọi để khởi tạo DbContext - Tôi nghĩ rằng đây sẽ là trách nhiệm của điểm vào của ứng dụng để khởi tạo tất cả các phụ thuộc cần thiết. Vì vậy, người tiêu dùng chỉ cần giới thiệu loại yêu cầu như một sự phụ thuộc trong hợp đồng riêng.
Fabio

@Fabio Có thể là vậy. (Trong trường hợp đó, trách nhiệm của người gọi sẽ cung cấp tài nguyên được khởi tạo khi khởi động ứng dụng cho phương thức / lớp được gọi.) Trong ví dụ của tôi, mặc dù vậy, đó không phải là một yêu cầu để giải thích khái niệm tiêm phụ thuộc .
Marc.2377

5

Các khái niệm trừu tượng thường được giải thích tốt hơn bằng cách sử dụng một sự tương tự trong thế giới thực. Đây là tương tự của tôi:

Bạn điều hành một cửa hàng bánh sandwich. Bạn làm bánh sandwich tuyệt vời, nhưng bạn không biết gì nhiều về bánh mì. Bạn chỉ có bánh mì trắng nhạt nhẽo. Bạn hoàn toàn tập trung vào lớp trên cùng mà bạn sử dụng để biến bánh mì thành bánh sandwich.

Tuy nhiên, một số khách hàng của bạn sẽ thực sự thích bánh mì nâu. Một số sẽ thích wholegrain. Dù sao thì bạn cũng không quan tâm, bạn có thể làm bất kỳ chiếc bánh sandwich tuyệt vời nào miễn là nó có kích thước tương tự. Bạn cũng thực sự không muốn phải chịu trách nhiệm bổ sung trong việc mua sắm một số loại bánh mì và giữ cho cổ phiếu tăng. Ngay cả khi bạn dự trữ một số loại bánh mì, sẽ luôn có một số khách hàng có một số hương vị kỳ lạ trong bánh mì mà bạn không thể thấy trước một cách hợp lý.

Vì vậy, bạn thiết lập một quy tắc mới: khách hàng mang bánh mì của riêng họ. Bạn không còn cung cấp bất kỳ bánh mì cho mình. Đây là một tình huống đôi bên cùng có lợi: khách hàng có được bánh mì chính xác mà họ muốn và bạn không còn phải bận tâm đến việc mua bánh mì mà bạn không quan tâm. Rốt cuộc, bạn là một người làm bánh sandwich, không phải là thợ làm bánh.

Ồ, và để chứa những khách hàng không muốn mua bánh mì của riêng họ, bạn mở một cửa hàng thứ hai bên cạnh bán bánh mì trắng nhạt nhẽo ban đầu của bạn. Những khách hàng không mang theo bánh mì của họ chỉ cần lấy một cái mặc định và sau đó đến với bạn để làm một chiếc bánh sandwich với nó.

Nó không hoàn hảo nhưng nó làm nổi bật tính năng chính: trao quyền kiểm soát cho người tiêu dùng . Win-win vốn có là bạn không còn phải có được sự phụ thuộc của riêng mình và người tiêu dùng của bạn không bị cản trở trong lựa chọn phụ thuộc của họ.


1
Tôi thích điều này nhưng OP đang tìm kiếm một lời giải thích cho các nhà phát triển . Một sự trừu tượng ban đầu là tốt, nhưng sớm hay muộn, nó sẽ cần một ví dụ thực tế.
Robbie Dee

1
@RobbieDee: Khi mục đích của mẫu rõ ràng, việc thực hiện nó cũng có xu hướng trở nên rõ ràng. Ví dụ, câu trả lời của Marc là hoàn toàn chính xác, nhưng tôi cảm thấy như lời giải thích đang bị sa lầy bởi bản chất phức tạp của tình huống ví dụ mà anh ta sử dụng. Điều này có nghĩa là "Nếu bạn muốn đóng một con tàu, đừng đánh trống người để thu thập gỗ và đừng giao cho họ nhiệm vụ và công việc, mà là dạy họ khao khát sự bao la vô tận của biển cả." . Thay vì giải thích những gì cần làm, tôi thích giải thích tại sao phải làm điều đó.
Flater

2
Tất nhiên bạn đúng, nhưng tôi không thể không nghĩ rằng tôi cần một ví dụ hữu hình - chẳng hạn như không có hệ thống tệp hoặc cơ sở dữ liệu thực sự để kích thích sự thèm ăn của tôi nhưng có lẽ đó chỉ là quan điểm nhà phát triển hẹp của tôi :)
Robbie Dee

1

Câu trả lời đơn giản cho điều đó:

Trước hết, một lớp nên có trách nhiệm được xác định rõ và mọi thứ nằm ngoài phạm vi này nên được giữ bên ngoài lớp đó. Như đã nói, Dependency Injection là khi bạn tiêm một chức năng từ lớp B khác vào lớp A bằng cách sử dụng trợ giúp từ "bên thứ ba" để đạt được sự phân tách mối quan tâm này, giúp lớp A hoàn thành một số hoạt động nằm ngoài phạm vi của nó.

.Net Core là một ví dụ khá hay mà bạn có thể đưa ra vì khung này sử dụng rất nhiều phép nội xạ phụ thuộc. Nói chung, các dịch vụ mà bạn muốn tiêm được đặt tại startup.cstệp.

Chắc chắn, sinh viên nên biết về một số khái niệm như đa hình, giao diện và các nguyên tắc Thiết kế OOP.


0

Có rất nhiều lông tơ và bunkum xung quanh, về bản chất, là một khái niệm đơn giản.

Cũng rất dễ bị sa lầy với " khung nào tôi nên sử dụng " khi bạn có thể làm điều đó khá đơn giản bằng mã.

Đây là định nghĩa cá nhân tôi sử dụng:

Cho hành vi X với sự phụ thuộc của Y. Phụ thuộc tiêm liên quan đến việc cơ sở cung cấp bất kỳ Y nào thỏa mãn các tiêu chí để trở thành một thể hiện của Y, ngay cả khi bạn không có.

Một số ví dụ có thể là Y là một hệ thống tệp hoặc kết nối cơ sở dữ liệu.

Các khung như moq cho phép nhân đôi (các phiên bản giả định của Y) được xác định bằng giao diện để có thể đưa vào một thể hiện của Y, ví dụ Y là kết nối cơ sở dữ liệu.

Thật dễ dàng để rơi vào cái bẫy tin rằng đây hoàn toàn là một mối quan tâm kiểm thử đơn vị nhưng nó là một mô hình rất hữu ích cho bất kỳ bit mã nào mà sự thay đổi được dự kiến ​​và có thể nói là dù sao đi nữa, đó là cách thực hành tốt.


0

Chúng tôi cung cấp hành vi của một chức năng trong thời gian chạy thông qua phương pháp chèn hành vi đó vào chức năng thông qua một tham số.

Mô hình chiến lược là một ví dụ tuyệt vời của tiêm phụ thuộc.


0

Để làm điều này đúng, trước tiên chúng ta phải xác định phụ thuộc và tiêm.

  • Phụ thuộc: bất kỳ tài nguyên mà một hoạt động cần.
  • Tiêm: truyền tài nguyên đó cho hoạt động, thường là đối số cho một phương thức.

Một ví dụ thô sơ sẽ là một phương thức thêm hai giá trị. Rõ ràng, phương pháp này cần các giá trị được thêm vào. Nếu chúng được cung cấp bằng cách chuyển chúng thành đối số, thì đây đã là một trường hợp tiêm phụ thuộc. Thay thế sẽ là thực hiện các toán hạng dưới dạng các thuộc tính hoặc các biến toàn cục. Bằng cách đó, không phụ thuộc sẽ được tiêm, phụ thuộc sẽ được trả trước bên ngoài.

Giả sử bạn sử dụng các thuộc tính thay thế và bạn đặt tên cho chúng là A và B. Nếu bạn thay đổi tên thành Op1 và Op2, bạn sẽ phá vỡ phương thức Add. Hoặc IDE của bạn sẽ cập nhật tất cả các tên cho bạn, vấn đề là phương pháp cũng cần được cập nhật vì nó có phụ thuộc vào tài nguyên bên ngoài.

Ví dụ này là cơ bản nhưng bạn có thể tưởng tượng các ví dụ phức tạp hơn trong đó phương thức thực hiện một thao tác trên một đối tượng như một hình ảnh hoặc nơi nó đọc từ một luồng tệp. Bạn có muốn phương pháp tiếp cận với hình ảnh, đòi hỏi nó phải biết nó ở đâu không? Không. Bạn có muốn phương thức tự mở tệp, yêu cầu nó biết nơi tìm tệp hoặc thậm chí để biết tệp sẽ được đọc từ tệp không? Không.

Vấn đề: giảm chức năng của một phương thức đối với hành vi cốt lõi của nó và tách rời phương thức khỏi môi trường của nó. Bạn có được cái đầu tiên bằng cách làm cái thứ hai, bạn có thể coi đây là định nghĩa của tiêm phụ thuộc.

Lợi ích: vì các phụ thuộc cho môi trường của phương thức đã được loại bỏ, các thay đổi đối với phương thức sẽ không ảnh hưởng đến môi trường và ngược lại. => Ứng dụng trở nên dễ dàng hơn để duy trì (sửa đổi).

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.