Đầu tiên, tôi muốn giải thích một giả định mà tôi đưa ra cho câu trả lời này. Điều này không phải lúc nào cũng đúng, nhưng khá thường xuyên:
Giao diện là tính từ; các lớp là danh từ.
(Trên thực tế, có những giao diện cũng là danh từ, nhưng tôi muốn khái quát ở đây.)
Vì vậy, ví dụ một giao diện có thể là một cái gì đó như IDisposable
, IEnumerable
hoặc IPrintable
. Một lớp là một triển khai thực tế của một hoặc nhiều giao diện trong số này: List
hoặc Map
cả hai có thể là các triển khai của IEnumerable
.
Để có được điểm: Thường thì các lớp của bạn phụ thuộc vào nhau. Ví dụ: bạn có thể có một Database
lớp truy cập cơ sở dữ liệu của bạn (hah, thật bất ngờ! ;-)), nhưng bạn cũng muốn lớp này đăng nhập về việc truy cập cơ sở dữ liệu. Giả sử bạn có một lớp khác Logger
, sau đó Database
có một phụ thuộc vào Logger
.
Càng xa càng tốt.
Bạn có thể mô hình hóa sự phụ thuộc này trong Database
lớp của bạn với dòng sau:
var logger = new Logger();
và mọi thứ đều ổn Sẽ tốt cho đến ngày bạn nhận ra rằng bạn cần một loạt các logger: Đôi khi bạn muốn đăng nhập vào bảng điều khiển, đôi khi vào hệ thống tệp, đôi khi sử dụng TCP / IP và máy chủ ghi nhật ký từ xa, v.v.
Và tất nhiên bạn KHÔNG muốn thay đổi tất cả mã của mình (trong khi đó bạn có ánh mắt của nó) và thay thế tất cả các dòng
var logger = new Logger();
bởi:
var logger = new TcpLogger();
Đầu tiên, đây không phải là niềm vui. Thứ hai, đây là lỗi dễ xảy ra. Thứ ba, đây là công việc ngu ngốc, lặp đi lặp lại cho một con khỉ được đào tạo. Vậy bạn làm gì?
Rõ ràng đó là một ý tưởng khá tốt để giới thiệu một giao diện ICanLog
(hoặc tương tự) được thực hiện bởi tất cả các logger khác nhau. Vì vậy, bước 1 trong mã của bạn là bạn làm:
ICanLog logger = new Logger();
Bây giờ kiểu suy luận không thay đổi kiểu nữa, bạn luôn có một giao diện duy nhất để phát triển. Bước tiếp theo là bạn không muốn new Logger()
lặp đi lặp lại. Vì vậy, bạn đặt độ tin cậy để tạo các thể hiện mới cho một lớp nhà máy trung tâm duy nhất và bạn nhận được mã như:
ICanLog logger = LoggerFactory.Create();
Nhà máy tự quyết định loại logger để tạo. Mã của bạn không còn quan tâm nữa và nếu bạn muốn thay đổi loại logger đang được sử dụng, bạn hãy thay đổi nó một lần : Bên trong nhà máy.
Bây giờ, tất nhiên, bạn có thể khái quát hóa nhà máy này và làm cho nó hoạt động cho bất kỳ loại nào:
ICanLog logger = TypeFactory.Create<ICanLog>();
Ở đâu đó TypeFactory cần dữ liệu cấu hình mà lớp thực tế sẽ khởi tạo khi một loại giao diện cụ thể được yêu cầu, vì vậy bạn cần ánh xạ. Tất nhiên bạn có thể thực hiện ánh xạ này bên trong mã của mình, nhưng sau đó thay đổi kiểu có nghĩa là biên dịch lại. Nhưng bạn cũng có thể đặt ánh xạ này vào trong tệp XML, vd. Điều này cho phép bạn thay đổi lớp thực sự được sử dụng ngay cả sau khi biên dịch thời gian (!), Điều đó có nghĩa là động, không cần biên dịch lại!
Để cho bạn một ví dụ hữu ích cho việc này: Hãy nghĩ về một phần mềm không đăng nhập bình thường, nhưng khi khách hàng của bạn gọi và yêu cầu trợ giúp vì anh ta có vấn đề, tất cả những gì bạn gửi cho anh ta là một tệp cấu hình XML được cập nhật và giờ anh ta đã có đăng nhập được kích hoạt và hỗ trợ của bạn có thể sử dụng các tệp nhật ký để giúp khách hàng của bạn.
Và bây giờ, khi bạn thay thế tên một chút, bạn sẽ kết thúc bằng một triển khai đơn giản của Trình định vị dịch vụ , đây là một trong hai mẫu của Đảo ngược điều khiển (vì bạn đảo ngược điều khiển ai quyết định lớp chính xác nào sẽ khởi tạo).
Tất cả trong tất cả điều này làm giảm sự phụ thuộc trong mã của bạn, nhưng bây giờ tất cả mã của bạn có một phụ thuộc vào trình định vị dịch vụ trung tâm, duy nhất.
Việc tiêm phụ thuộc bây giờ là bước tiếp theo trong dòng này: Hãy loại bỏ sự phụ thuộc duy nhất này vào trình định vị dịch vụ: Thay vì các lớp khác nhau yêu cầu trình định vị dịch vụ triển khai cho một giao diện cụ thể, bạn - một lần nữa - hoàn nguyên quyền kiểm soát ai sẽ khởi tạo cái gì .
Với phép nội xạ phụ thuộc, Database
lớp của bạn hiện có một hàm tạo yêu cầu tham số kiểu ICanLog
:
public Database(ICanLog logger) { ... }
Bây giờ cơ sở dữ liệu của bạn luôn có một trình ghi nhật ký để sử dụng, nhưng nó không biết thêm trình ghi nhật ký này đến từ đâu nữa.
Và đây là lúc khung DI phát huy tác dụng: Bạn định cấu hình ánh xạ của mình một lần nữa, sau đó yêu cầu khung DI của bạn khởi tạo ứng dụng cho bạn. Vì Application
lớp yêu cầu ICanPersistData
triển khai, một thể hiện của Database
được chèn - nhưng trước tiên, nó phải tạo một thể hiện của loại logger được cấu hình cho ICanLog
. Và như thế ...
Vì vậy, để cắt ngắn một câu chuyện dài: Tiêm phụ thuộc là một trong hai cách để loại bỏ sự phụ thuộc trong mã của bạn. Nó rất hữu ích cho việc thay đổi cấu hình sau thời gian biên dịch, và đó là một điều tuyệt vời để thử nghiệm đơn vị (vì nó giúp dễ dàng tiêm cuống và / hoặc giả).
Trong thực tế, có những điều bạn không thể làm nếu không có bộ định vị dịch vụ (ví dụ: nếu bạn không biết trước có bao nhiêu trường hợp bạn cần một giao diện cụ thể: Khung DI luôn chỉ tiêm một phiên bản cho mỗi tham số, nhưng bạn có thể gọi một bộ định vị dịch vụ bên trong một vòng lặp, dĩ nhiên), do đó, hầu hết các khung DI cũng cung cấp một bộ định vị dịch vụ.
Nhưng về cơ bản, đó là nó.
PS: Những gì tôi mô tả ở đây là một kỹ thuật gọi là constructor tiêm , cũng có tiêm thuộc tính trong đó không phải là tham số của hàm tạo, nhưng các thuộc tính đang được sử dụng để xác định và giải quyết các phụ thuộc. Hãy nghĩ về tiêm tài sản như là một phụ thuộc tùy chọn, và tiêm xây dựng là phụ thuộc bắt buộc. Nhưng thảo luận về điều này là vượt quá phạm vi của câu hỏi này.