Đã có một số câu hỏi được đăng với các câu hỏi cụ thể về tiêm phụ thuộc , chẳng hạn như khi nào nên sử dụng nó và khung nào dành cho nó. Tuy nhiên,
Tiêm phụ thuộc là gì và khi nào / tại sao nên hay không nên sử dụng?
Đã có một số câu hỏi được đăng với các câu hỏi cụ thể về tiêm phụ thuộc , chẳng hạn như khi nào nên sử dụng nó và khung nào dành cho nó. Tuy nhiên,
Tiêm phụ thuộc là gì và khi nào / tại sao nên hay không nên sử dụng?
Câu trả lời:
Dependency Injection đang truyền phụ thuộc vào các đối tượng hoặc khung khác (trình tiêm phụ thuộc).
Phụ thuộc tiêm làm cho thử nghiệm dễ dàng hơn. Việc tiêm có thể được thực hiện thông qua các nhà xây dựng .
SomeClass()
có hàm tạo của nó như sau:
public SomeClass() {
myObject = Factory.getObject();
}
Vấn đề : Nếu myObject
liên quan đến các tác vụ phức tạp như truy cập đĩa hoặc truy cập mạng, thật khó để thực hiện kiểm tra đơn vị SomeClass()
. Các lập trình viên phải chế giễu myObject
và có thể chặn cuộc gọi của nhà máy.
Giải pháp thay thế :
myObject
vào như là một đối số cho các nhà xây dựngpublic SomeClass (MyClass myObject) {
this.myObject = myObject;
}
myObject
có thể được thông qua trực tiếp mà làm cho thử nghiệm dễ dàng hơn.
Khó có thể cô lập các thành phần trong thử nghiệm đơn vị mà không cần tiêm phụ thuộc.
Vào năm 2013, khi tôi viết câu trả lời này, đây là một chủ đề chính trên Blog Thử nghiệm của Google . Nó vẫn là lợi thế lớn nhất đối với tôi, vì các lập trình viên không phải lúc nào cũng cần thêm sự linh hoạt trong thiết kế thời gian chạy của họ (ví dụ, đối với trình định vị dịch vụ hoặc các mẫu tương tự). Các lập trình viên thường cần cách ly các lớp trong quá trình thử nghiệm.
Định nghĩa tốt nhất mà tôi tìm thấy cho đến nay là của James Shore :
"Dependency Injection" là một thuật ngữ 25 đô la cho khái niệm 5 xu. [...] Tiêm phụ thuộc có nghĩa là đưa ra một đối tượng các biến đối tượng của nó. [...].
Có một bài viết của Martin Fowler cũng có thể hữu ích.
Việc tiêm phụ thuộc về cơ bản là cung cấp các đối tượng mà một đối tượng cần (phụ thuộc của nó) thay vì tự nó xây dựng chúng. Đây là một kỹ thuật rất hữu ích để thử nghiệm, vì nó cho phép các phụ thuộc bị chế giễu hoặc bỏ đi.
Phụ thuộc có thể được tiêm vào các đối tượng bằng nhiều phương tiện (chẳng hạn như tiêm xây dựng hoặc tiêm setter). Người ta thậm chí có thể sử dụng các khung tiêm phụ thuộc chuyên biệt (ví dụ Spring) để làm điều đó, nhưng chắc chắn chúng không bắt buộc. Bạn không cần những khuôn khổ đó để tiêm phụ thuộc. Ngay lập tức và truyền đối tượng (phụ thuộc) rõ ràng cũng giống như tiêm theo khung.
Tôi tìm thấy ví dụ hài hước này về mặt khớp nối lỏng lẻo :
Bất kỳ ứng dụng nào cũng bao gồm nhiều đối tượng cộng tác với nhau để thực hiện một số nội dung hữu ích. Theo truyền thống, mỗi đối tượng chịu trách nhiệm có được các tham chiếu riêng của mình đến các đối tượng phụ thuộc (phụ thuộc) mà nó cộng tác. Điều này dẫn đến các lớp kết hợp cao và mã khó kiểm tra.
Ví dụ, hãy xem xét một Car
đối tượng.
A Car
phụ thuộc vào bánh xe, động cơ, nhiên liệu, pin, vv để chạy. Theo truyền thống, chúng tôi xác định thương hiệu của các đối tượng phụ thuộc như vậy cùng với định nghĩa của Car
đối tượng.
Không phụ thuộc tiêm (DI):
class Car{
private Wheel wh = new NepaliRubberWheel();
private Battery bt = new ExcideBattery();
//The rest
}
Ở đây, Car
đối tượng chịu trách nhiệm tạo các đối tượng phụ thuộc.
Điều gì sẽ xảy ra nếu chúng ta muốn thay đổi loại đối tượng phụ thuộc của nó - giả sử Wheel
- sau khi NepaliRubberWheel()
chọc thủng ban đầu ? Chúng ta cần tạo lại đối tượng Car với phụ thuộc mới ChineseRubberWheel()
, nhưng chỉ Car
nhà sản xuất mới có thể làm điều đó.
Vậy thì điều gì Dependency Injection
làm cho chúng ta ...?
Khi sử dụng phép tiêm phụ thuộc, các đối tượng được cung cấp các phụ thuộc của chúng tại thời gian chạy thay vì thời gian biên dịch (thời gian sản xuất xe hơi) . Vì vậy, bây giờ chúng ta có thể thay đổi Wheel
bất cứ khi nào chúng ta muốn. Ở đây, dependency
( wheel
) có thể được đưa vào Car
trong thời gian chạy.
Sau khi sử dụng tiêm phụ thuộc:
Ở đây, chúng tôi đang tiêm các phụ thuộc (Bánh xe và Pin) khi chạy. Do đó, thuật ngữ: Phụ thuộc tiêm.
class Car{
private Wheel wh; // Inject an Instance of Wheel (dependency of car) at runtime
private Battery bt; // Inject an Instance of Battery (dependency of car) at runtime
Car(Wheel wh,Battery bt) {
this.wh = wh;
this.bt = bt;
}
//Or we can have setters
void setWheel(Wheel wh) {
this.wh = wh;
}
}
Nguồn: Tìm hiểu tiêm phụ thuộc
new
một lốp xe? Tôi không. Tất cả những gì tôi phải làm là mua (tiêm qua param) từ họ, cài đặt và wah-lah! Vì vậy, quay trở lại lập trình, giả sử một dự án C # cần sử dụng thư viện / lớp hiện có, có hai cách để chạy / gỡ lỗi, tham chiếu thêm 1 vào toàn bộ dự án này
new
nó, tùy chọn 2 là vượt qua nó dưới dạng param. Có thể không chính xác, nhưng đơn giản ngu ngốc dễ hiểu.
Dependency Injection là một cách thực hành trong đó các đối tượng được thiết kế theo cách mà chúng nhận được các thể hiện của các đối tượng từ các đoạn mã khác, thay vì xây dựng chúng bên trong. Điều này có nghĩa là bất kỳ đối tượng nào thực hiện giao diện mà đối tượng yêu cầu có thể được thay thế mà không thay đổi mã, giúp đơn giản hóa việc kiểm tra và cải thiện việc tách rời.
Ví dụ, hãy xem xét các phân đoạn này:
public class PersonService {
public void addManager( Person employee, Person newManager ) { ... }
public void removeManager( Person employee, Person oldManager ) { ... }
public Group getGroupByManager( Person manager ) { ... }
}
public class GroupMembershipService() {
public void addPersonToGroup( Person person, Group group ) { ... }
public void removePersonFromGroup( Person person, Group group ) { ... }
}
Trong ví dụ này, việc thực hiện PersonService::addManager
và PersonService::removeManager
sẽ cần một thể hiện GroupMembershipService
để thực hiện công việc của nó. Nếu không có Dependency Injection, cách làm truyền thống này sẽ là khởi tạo một cái mới GroupMembershipService
trong hàm tạo PersonService
và sử dụng thuộc tính thể hiện đó trong cả hai hàm. Tuy nhiên, nếu hàm tạo của nó GroupMembershipService
có nhiều thứ mà nó yêu cầu, hoặc tệ hơn nữa, có một số "setters" khởi tạo cần được gọi trên GroupMembershipService
, mã phát triển khá nhanh và PersonService
bây giờ không chỉ phụ thuộc vào GroupMembershipService
mà còn phụ thuộc vào mọi thứ khác. GroupMembershipService
phụ thuộc. Hơn nữa, liên kết đến GroupMembershipService
được mã hóa cứng PersonService
có nghĩa là bạn không thể "giả"GroupMembershipService
cho mục đích thử nghiệm hoặc để sử dụng mẫu chiến lược trong các phần khác nhau của ứng dụng của bạn.
Với Dependency Injection, thay vì khởi tạo GroupMembershipService
bên trong của bạn PersonService
, bạn sẽ chuyển nó vào hàm PersonService
tạo hoặc nếu không thêm Thuộc tính (getter và setter) để đặt phiên bản cục bộ của nó. Điều này có nghĩa là bạn PersonService
không còn phải lo lắng về cách tạo một GroupMembershipService
, nó chỉ chấp nhận những gì nó đưa ra và làm việc với chúng. Điều này cũng có nghĩa là bất cứ thứ gì thuộc lớp con GroupMembershipService
hoặc thực hiện GroupMembershipService
giao diện đều có thể được "chèn" vào PersonService
và PersonService
không cần biết về thay đổi.
Câu trả lời được chấp nhận là một câu trả lời hay - nhưng tôi muốn nói thêm rằng DI rất giống với cách tránh cổ điển của các hằng số được mã hóa cứng trong mã.
Khi bạn sử dụng một số hằng như tên cơ sở dữ liệu, bạn sẽ nhanh chóng di chuyển nó từ bên trong mã sang một tệp cấu hình nào đó và chuyển một biến chứa giá trị đó đến nơi cần thiết. Lý do để làm điều đó là các hằng số này thường thay đổi thường xuyên hơn phần còn lại của mã. Ví dụ: nếu bạn muốn kiểm tra mã trong cơ sở dữ liệu thử nghiệm.
DI tương tự như thế này trong thế giới lập trình hướng đối tượng. Các giá trị ở đó thay vì bằng chữ không đổi là toàn bộ các đối tượng - nhưng lý do để di chuyển mã tạo ra chúng từ mã lớp là tương tự nhau - các đối tượng thay đổi thường xuyên hơn sau đó mã sử dụng chúng. Một trường hợp quan trọng khi cần thay đổi như vậy là các xét nghiệm.
Hãy thử ví dụ đơn giản với các lớp Xe hơi và Động cơ , bất kỳ chiếc xe nào cũng cần một động cơ để đi bất cứ đâu, ít nhất là bây giờ. Vì vậy, dưới đây mã sẽ trông như thế nào mà không cần tiêm phụ thuộc.
public class Car
{
public Car()
{
GasEngine engine = new GasEngine();
engine.Start();
}
}
public class GasEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
Và để khởi tạo lớp Xe, chúng ta sẽ sử dụng mã tiếp theo:
Car car = new Car();
Vấn đề với mã này mà chúng tôi đã liên kết chặt chẽ với GasEngine và nếu chúng tôi quyết định đổi nó thành ElectricalEngine thì chúng tôi sẽ cần phải viết lại lớp Xe. Và ứng dụng càng lớn thì càng có nhiều vấn đề và đau đầu, chúng ta sẽ phải thêm và sử dụng loại động cơ mới.
Nói cách khác với cách tiếp cận này là lớp Xe cao cấp của chúng tôi phụ thuộc vào lớp GasEngine cấp thấp hơn vi phạm Nguyên tắc đảo ngược phụ thuộc (DIP) từ RẮN. DIP gợi ý rằng chúng ta nên phụ thuộc vào trừu tượng, không phải các lớp cụ thể. Vì vậy, để đáp ứng điều này, chúng tôi giới thiệu giao diện IEngine và viết lại mã như dưới đây:
public interface IEngine
{
void Start();
}
public class GasEngine : IEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
public class ElectricityEngine : IEngine
{
public void Start()
{
Console.WriteLine("I am electrocar");
}
}
public class Car
{
private readonly IEngine _engine;
public Car(IEngine engine)
{
_engine = engine;
}
public void Run()
{
_engine.Start();
}
}
Bây giờ lớp Xe của chúng tôi chỉ phụ thuộc vào giao diện IEngine, không phải là một triển khai cụ thể của động cơ. Bây giờ, mẹo duy nhất là làm thế nào để chúng ta tạo ra một phiên bản của Xe và cung cấp cho nó một lớp Động cơ bê tông thực tế như GasEngine hoặc ElectricalEngine. Đó là nơi mà Dependency Injection xuất hiện.
Car gasCar = new Car(new GasEngine());
gasCar.Run();
Car electroCar = new Car(new ElectricityEngine());
electroCar.Run();
Ở đây, về cơ bản, chúng tôi đưa (vượt qua) sự phụ thuộc của chúng tôi (ví dụ Động cơ) vào Trình tạo ô tô. Vì vậy, bây giờ các lớp của chúng ta có khớp nối lỏng lẻo giữa các đối tượng và các phụ thuộc của chúng và chúng ta có thể dễ dàng thêm các loại động cơ mới mà không cần thay đổi lớp Xe.
Lợi ích chính của Tiêm phụ thuộc là các lớp được liên kết lỏng lẻo hơn, vì chúng không có các phụ thuộc được mã hóa cứng. Điều này tuân theo Nguyên tắc đảo ngược phụ thuộc, đã được đề cập ở trên. Thay vì tham chiếu các triển khai cụ thể, các lớp yêu cầu trừu tượng hóa (thường là các giao diện ) được cung cấp cho chúng khi lớp được xây dựng.
Vì vậy, cuối cùng tiêm Dependency chỉ là một kỹ thuật để đạt được khớp nối lỏng lẻo giữa các đối tượng và các phụ thuộc của chúng. Thay vì trực tiếp khởi tạo các phụ thuộc mà lớp cần để thực hiện các hành động của nó, các phụ thuộc được cung cấp cho lớp (thường xuyên nhất) thông qua phép tiêm của hàm tạo.
Ngoài ra, khi chúng ta có nhiều phụ thuộc, nên sử dụng các thùng chứa Inversion of Control (IoC) mà chúng ta có thể biết giao diện nào sẽ được ánh xạ tới các triển khai cụ thể nào cho tất cả các phụ thuộc của chúng ta và chúng ta có thể giải quyết các phụ thuộc đó cho chúng ta khi nó xây dựng đối tượng của chúng tôi. Ví dụ: chúng ta có thể chỉ định trong ánh xạ cho bộ chứa IoC rằng phụ thuộc IEngine sẽ được ánh xạ tới lớp GasEngine và khi chúng ta yêu cầu bộ chứa IoC ví dụ về lớp Xe của chúng ta , nó sẽ tự động xây dựng lớp Xe của chúng ta với phụ thuộc GasEngine thông qua tại.
CẬP NHẬT: Đã xem khóa học về EF Core từ Julie Lerman gần đây và cũng thích định nghĩa ngắn của cô về DI.
Tiêm phụ thuộc là một mẫu để cho phép ứng dụng của bạn tiêm các đối tượng một cách nhanh chóng đến các lớp cần chúng, mà không buộc các lớp đó phải chịu trách nhiệm về các đối tượng đó. Nó cho phép mã của bạn được kết nối lỏng lẻo hơn và Entity Framework Core cắm vào cùng hệ thống dịch vụ này.
Hãy tưởng tượng rằng bạn muốn đi câu cá:
Nếu không tiêm phụ thuộc, bạn cần phải tự chăm sóc mọi thứ. Bạn cần phải tìm một chiếc thuyền, để mua một chiếc cần câu, để tìm mồi, v.v ... Tất nhiên là có thể, nhưng nó đặt rất nhiều trách nhiệm lên bạn. Về mặt phần mềm, điều đó có nghĩa là bạn phải thực hiện tra cứu tất cả những điều này.
Với tiêm phụ thuộc, người khác sẽ chăm sóc tất cả các chế phẩm và làm cho các thiết bị cần thiết có sẵn cho bạn. Bạn sẽ nhận được ("được tiêm") thuyền, cần câu và mồi - tất cả đã sẵn sàng để sử dụng.
Đây là lời giải thích đơn giản nhất về Dependency Injection và Dependency Injection Container mà tôi từng thấy:
Phụ thuộc Tiêm phụ thuộc và Tiêm phụ thuộc là những thứ khác nhau:
Bạn không cần một container để thực hiện tiêm phụ thuộc. Tuy nhiên một container có thể giúp bạn.
Không "tiêm phụ thuộc" chỉ có nghĩa là sử dụng các hàm tạo tham số và setters công cộng?
Bài viết của James Shore cho thấy các ví dụ sau đây để so sánh .
Xây dựng mà không cần tiêm phụ thuộc:
public class Example { private DatabaseThingie myDatabase; public Example() { myDatabase = new DatabaseThingie(); } public void doStuff() { ... myDatabase.getData(); ... } }
Xây dựng với tiêm phụ thuộc:
public class Example { private DatabaseThingie myDatabase; public Example(DatabaseThingie useThisDatabaseInstead) { myDatabase = useThisDatabaseInstead; } public void doStuff() { ... myDatabase.getData(); ... } }
new DatabaseThingie()
không tạo ra một cá thể myDatabase hợp lệ.
Để làm cho khái niệm Dependency tiêm đơn giản để hiểu. Hãy lấy một ví dụ về nút chuyển đổi để bật (bật / tắt) bóng đèn.
Switch cần biết trước tôi đang kết nối với bóng đèn nào (phụ thuộc mã hóa cứng). Vì thế,
Công tắc -> PermanentBulb // công tắc được kết nối trực tiếp với bóng đèn vĩnh viễn, không thể kiểm tra dễ dàng
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}
Switch chỉ biết tôi cần bật / tắt bất cứ bóng đèn nào được chuyển cho tôi. Vì thế,
Chuyển đổi -> Bóng đèn1 HOẶC Bóng đèn2 HOẶC NightBulb (phụ thuộc được tiêm)
Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}
Sửa đổi ví dụ James cho Switch và Bóng đèn:
public class SwitchTest {
TestToggleBulb() {
MockBulb mockbulb = new MockBulb();
// MockBulb is a subclass of Bulb, so we can
// "inject" it here:
Switch switch = new Switch(mockBulb);
switch.ToggleBulb();
mockBulb.AssertToggleWasCalled();
}
}
public class Switch {
private Bulb myBulb;
public Switch() {
myBulb = new Bulb();
}
public Switch(Bulb useThisBulbInstead) {
myBulb = useThisBulbInstead;
}
public void ToggleBulb() {
...
myBulb.Toggle();
...
}
}`
Phụ thuộc tiêm (DI) là gì?
Như những người khác đã nói, Dependency Injection (DI) loại bỏ trách nhiệm tạo trực tiếp và quản lý tuổi thọ của các trường hợp đối tượng khác mà lớp lợi ích của chúng tôi (lớp người tiêu dùng) phụ thuộc (theo nghĩa UML ). Thay vào đó, các trường hợp này được chuyển đến lớp người tiêu dùng của chúng tôi, thường là các tham số của hàm tạo hoặc thông qua các thuộc tính (việc quản lý đối tượng phụ thuộc và chuyển đến lớp người tiêu dùng thường được thực hiện bởi bộ chứa Inversion of Control (IoC) , nhưng đó là một chủ đề khác) .
DI, Dip và RẮN
Cụ thể, trong mô hình của các nguyên tắc RẮN của Thiết kế hướng đối tượng của Robert C Martin , DI
là một trong những triển khai có thể của Nguyên tắc đảo ngược phụ thuộc (DIP) . Các DIP là D
của SOLID
thần chú - triển khai DIP khác bao gồm Locator Service, và các mẫu Plugin.
Mục tiêu của DIP là tách rời các phụ thuộc cụ thể, chặt chẽ giữa các lớp và thay vào đó, để nới lỏng khớp nối bằng cách trừu tượng hóa, có thể đạt được thông qua interface
, abstract class
hoặc pure virtual class
, tùy thuộc vào ngôn ngữ và cách tiếp cận được sử dụng.
Không có DIP, mã của chúng tôi (tôi đã gọi đây là 'lớp tiêu thụ') được kết hợp trực tiếp với một phụ thuộc cụ thể và cũng thường chịu trách nhiệm về cách nhận và quản lý, ví dụ về sự phụ thuộc này, tức là về mặt khái niệm:
"I need to create/use a Foo and invoke method `GetBar()`"
Trong khi sau khi áp dụng DIP, yêu cầu được nới lỏng và mối quan tâm về việc có được và quản lý tuổi thọ của sự Foo
phụ thuộc đã được loại bỏ:
"I need to invoke something which offers `GetBar()`"
Tại sao nên sử dụng DIP (và DI)?
Việc tách rời các phụ thuộc giữa các lớp theo cách này cho phép dễ dàng thay thế các lớp phụ thuộc này bằng các triển khai khác cũng đáp ứng các điều kiện tiên quyết của sự trừu tượng hóa (ví dụ: sự phụ thuộc có thể được chuyển đổi bằng cách thực hiện khác của cùng một giao diện). Hơn nữa, như những người khác đã đề cập, có thể là những lý do phổ biến nhất để các lớp tách thông qua DIP là cho phép một lớp tiêu thụ được thử nghiệm trong sự cô lập, như những phụ thuộc cùng bây giờ có thể được stubbed và / hoặc chế giễu.
Một hậu quả của DI là việc quản lý tuổi thọ của các thể hiện đối tượng phụ thuộc không còn được kiểm soát bởi một lớp tiêu thụ, vì đối tượng phụ thuộc giờ được chuyển vào lớp tiêu thụ (thông qua hàm tạo hoặc hàm setter).
Điều này có thể được xem theo những cách khác nhau:
Create
nhà máy khi cần thiết và loại bỏ các trường hợp này sau khi hoàn thành.Khi nào sử dụng DI?
MyDepClass
là an toàn cho chuỗi - điều gì sẽ xảy ra nếu chúng ta biến nó thành một cá thể và tiêm cùng một ví dụ cho tất cả người tiêu dùng?)Thí dụ
Đây là một triển khai C # đơn giản. Đưa ra lớp tiêu thụ dưới đây:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
Mặc dù có vẻ vô hại, nó có hai static
phụ thuộc vào hai lớp khác System.DateTime
và System.Console
không chỉ giới hạn các tùy chọn đầu ra đăng nhập (đăng nhập vào bảng điều khiển sẽ vô dụng nếu không có ai xem), nhưng tệ hơn, rất khó để tự động kiểm tra phụ thuộc vào một đồng hồ hệ thống không xác định.
Tuy nhiên, chúng ta có thể áp dụng DIP
cho lớp này, bằng cách trừu tượng hóa mối quan tâm của dấu thời gian như là một phụ thuộc và MyLogger
chỉ khớp với một giao diện đơn giản:
public interface IClock
{
DateTime Now { get; }
}
Chúng ta cũng có thể nới lỏng sự phụ thuộc vào Console
một sự trừu tượng, chẳng hạn như a TextWriter
. Sự phụ thuộc tiêm thường được triển khai như là một phép constructor
tiêm (chuyển một sự trừu tượng đến một phụ thuộc như là một tham số cho hàm tạo của một lớp tiêu thụ) hoặc Setter Injection
(chuyển sự phụ thuộc thông qua một setXyz()
setter hoặc .Net property {set;}
được xác định). Trình xây dựng tiêm được ưu tiên, vì điều này đảm bảo lớp sẽ ở trạng thái chính xác sau khi xây dựng và cho phép các trường phụ thuộc bên trong được đánh dấu là readonly
(C #) hoặc final
(Java). Vì vậy, bằng cách sử dụng hàm xây dựng trên ví dụ trên, điều này cho chúng ta:
public class MyLogger : ILogger // Others will depend on our logger.
{
private readonly TextWriter _output;
private readonly IClock _clock;
// Dependencies are injected through the constructor
public MyLogger(TextWriter stream, IClock clock)
{
_output = stream;
_clock = clock;
}
public void LogRecord(string somethingToLog)
{
// We can now use our dependencies through the abstraction
// and without knowledge of the lifespans of the dependencies
_output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
}
}
(Một Clock
nhu cầu cụ thể cần được cung cấp, tất nhiên có thể hoàn nguyên DateTime.Now
và hai phụ thuộc cần được cung cấp bởi một bộ chứa IoC thông qua việc tiêm constructor)
Có thể xây dựng Kiểm tra đơn vị tự động, điều này chứng minh chắc chắn rằng trình ghi nhật ký của chúng tôi đang hoạt động chính xác, vì giờ đây chúng tôi có quyền kiểm soát các phụ thuộc - thời gian và chúng tôi có thể theo dõi kết quả đầu ra bằng văn bản:
[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
// Arrange
var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
var fakeConsole = new StringWriter();
// Act
new MyLogger(fakeConsole, mockClock.Object)
.LogRecord("Foo");
// Assert
Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}
Bước tiếp theo
Việc tiêm phụ thuộc luôn được liên kết với bộ chứa Inversion of Control (IoC) , để tiêm (cung cấp) các trường hợp phụ thuộc cụ thể và để quản lý các trường hợp tuổi thọ. Trong quá trình cấu hình / bootstrapping, IoC
các thùng chứa cho phép xác định các mục sau:
IBar
, trả lại một ConcreteBar
thể hiện" )IDisposable
và sẽ chịu trách nhiệm về các Disposing
phụ thuộc phù hợp với quản lý tuổi thọ được cấu hình.Thông thường, một khi các bộ chứa IoC đã được cấu hình / bootstrapping, chúng hoạt động trơn tru trong nền cho phép người viết mã tập trung vào mã trong tay thay vì lo lắng về sự phụ thuộc.
Chìa khóa của mã thân thiện với DI là tránh sự kết hợp tĩnh của các lớp và không sử dụng new () để tạo Dependencies
Như ví dụ trên, việc tách rời các phụ thuộc đòi hỏi một số nỗ lực thiết kế và đối với nhà phát triển, có một sự thay đổi mô hình cần thiết để phá vỡ thói quen sử dụng new
phụ thuộc trực tiếp và thay vào đó tin tưởng vào container để quản lý các phụ thuộc.
Nhưng lợi ích thì rất nhiều, đặc biệt là ở khả năng kiểm tra kỹ lưỡng lớp bạn quan tâm.
Lưu ý : Việc tạo / ánh xạ / phép chiếu (thông qua new ..()
) của POCO / POJO / Tuần tự hóa DTOs / Đồ thị thực thể / Các phép chiếu JSON ẩn danh et al - tức là các lớp hoặc bản ghi "Chỉ dữ liệu" - được sử dụng hoặc trả về từ các phương thức không được coi là Phụ thuộc (trong Ý nghĩa UML) và không chịu sự điều chỉnh của DI. Sử dụng new
để chiếu những thứ này là tốt.
Toàn bộ quan điểm của Dependency Injection (DI) là giữ cho mã nguồn ứng dụng sạch và ổn định :
Thực tế, mọi mẫu thiết kế tách biệt mối quan tâm để thực hiện các thay đổi trong tương lai ảnh hưởng đến các tệp tối thiểu.
Miền cụ thể của DI là ủy quyền cấu hình và khởi tạo phụ thuộc.
Nếu bạn thỉnh thoảng làm việc bên ngoài Java, hãy nhớ lại cách source
thường được sử dụng trong nhiều ngôn ngữ script (Shell, Tcl, v.v. hoặc thậm chí import
trong Python bị lạm dụng cho mục đích này).
Xem xét dependent.sh
kịch bản đơn giản :
#!/bin/sh
# Dependent
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
Kịch bản phụ thuộc: nó sẽ không tự thực hiện thành công ( archive_files
không được xác định).
Bạn xác định archive_files
trong archive_files_zip.sh
tập lệnh thực hiện (sử dụng zip
trong trường hợp này):
#!/bin/sh
# Dependency
function archive_files {
zip files.zip "$@"
}
Thay vì source
-ing tập lệnh triển khai trực tiếp trong tập lệnh phụ thuộc, bạn sử dụng injector.sh
"vùng chứa" bao bọc cả hai "thành phần":
#!/bin/sh
# Injector
source ./archive_files_zip.sh
source ./dependent.sh
Sự archive_files
phụ thuộc vừa được đưa vào tập lệnh phụ thuộc .
Bạn có thể đã tiêm phụ thuộc mà thực hiện archive_files
bằng cách sử dụng tar
hoặc xz
.
Nếu dependent.sh
tập lệnh sử dụng phụ thuộc trực tiếp, cách tiếp cận sẽ được gọi là tra cứu phụ thuộc (ngược lại với nội xạ phụ thuộc ):
#!/bin/sh
# Dependent
# dependency look-up
source ./archive_files_zip.sh
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
Bây giờ vấn đề là "thành phần" phụ thuộc phải tự thực hiện khởi tạo.
Mã nguồn của "thành phần" không sạch cũng không ổn định vì mọi thay đổi trong khởi tạo phụ thuộc đều yêu cầu phát hành mới cho tệp mã nguồn của "thành phần".
DI không được nhấn mạnh và phổ biến rộng rãi như trong các khung công tác Java.
Nhưng đó là một cách tiếp cận chung để phân chia mối quan tâm về:
Chỉ sử dụng cấu hình với tra cứu phụ thuộc không giúp ích gì vì số lượng tham số cấu hình có thể thay đổi theo phụ thuộc (ví dụ: loại xác thực mới) cũng như số loại phụ thuộc được hỗ trợ (ví dụ: loại cơ sở dữ liệu mới).
Tất cả các câu trả lời trên đều tốt, mục đích của tôi là giải thích khái niệm một cách đơn giản để bất kỳ ai không có kiến thức lập trình cũng có thể hiểu khái niệm
Phụ thuộc tiêm là một trong những mẫu thiết kế giúp chúng ta tạo ra các hệ thống phức tạp một cách đơn giản hơn.
Chúng ta có thể thấy rất nhiều ứng dụng của mẫu này trong cuộc sống hàng ngày của chúng ta. Một số ví dụ là máy ghi âm, VCD, CD Drive, v.v.
Hình ảnh trên là hình ảnh của máy ghi âm cầm tay reel-to-reel, giữa thế kỷ 20. Nguồn .
Mục đích chính của máy ghi âm là ghi hoặc phát lại âm thanh.
Trong khi thiết kế một hệ thống, nó đòi hỏi một cuộn để ghi hoặc phát lại âm thanh hoặc âm nhạc. Có hai khả năng để thiết kế hệ thống này.
Nếu chúng ta sử dụng cái đầu tiên, chúng ta cần mở máy để thay đổi trục quay. nếu chúng ta chọn cách thứ hai, đó là đặt một cái móc cho guồng, chúng ta sẽ có thêm lợi ích khi chơi bất kỳ bản nhạc nào bằng cách thay đổi guồng. và cũng giảm chức năng chỉ để chơi bất cứ thứ gì trong guồng.
Giống như tiêm phụ thuộc khôn ngoan là quá trình bên ngoài các phụ thuộc chỉ tập trung vào chức năng cụ thể của thành phần để các thành phần độc lập có thể được ghép với nhau để tạo thành một hệ thống phức tạp.
Những lợi ích chính chúng tôi đạt được bằng cách sử dụng tiêm phụ thuộc.
Bây giờ một ngày những khái niệm này tạo thành nền tảng của các khung nổi tiếng trong thế giới lập trình. Spring Angular v.v ... là các khung phần mềm nổi tiếng được xây dựng trên đỉnh của khái niệm này
Phép nội xạ phụ thuộc là một mẫu được sử dụng để tạo các thể hiện của các đối tượng mà các đối tượng khác dựa vào mà không biết tại thời điểm biên dịch lớp nào sẽ được sử dụng để cung cấp chức năng đó hoặc đơn giản là cách tiêm các thuộc tính cho một đối tượng được gọi là tiêm phụ thuộc.
Ví dụ cho tiêm phụ thuộc
Trước đây chúng tôi đang viết mã như thế này
Public MyClass{
DependentClass dependentObject
/*
At somewhere in our code we need to instantiate
the object with new operator inorder to use it or perform some method.
*/
dependentObject= new DependentClass();
dependentObject.someMethod();
}
Với tiêm phụ thuộc, người tiêm phụ thuộc sẽ khởi động việc khởi tạo cho chúng tôi
Public MyClass{
/* Dependency injector will instantiate object*/
DependentClass dependentObject
/*
At somewhere in our code we perform some method.
The process of instantiation will be handled by the dependency injector
*/
dependentObject.someMethod();
}
Bạn cũng có thể đọc
Dependency Injection (DI) có nghĩa là tách rời các đối tượng phụ thuộc lẫn nhau. Nói đối tượng A phụ thuộc vào đối tượng B nên ý tưởng là tách rời các đối tượng này với nhau. Chúng ta không cần mã cứng đối tượng bằng cách sử dụng từ khóa mới thay vì chia sẻ các phụ thuộc vào các đối tượng trong thời gian chạy bất chấp thời gian biên dịch. Nếu chúng ta nói về
Chúng ta không cần mã cứng đối tượng bằng từ khóa mới thay vì xác định phụ thuộc bean trong tệp cấu hình. Các container mùa xuân sẽ chịu trách nhiệm móc lên tất cả.
IOC là một khái niệm chung và nó có thể được thể hiện theo nhiều cách khác nhau và Dependency Injection là một ví dụ cụ thể của IOC.
DI dựa trên trình xây dựng được hoàn thành khi container gọi một hàm tạo của lớp với một số đối số, mỗi đối số biểu thị một phụ thuộc vào lớp khác.
public class Triangle {
private String type;
public String getType(){
return type;
}
public Triangle(String type){ //constructor injection
this.type=type;
}
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
<constructor-arg value="20"/>
</bean>
DI dựa trên Setter được thực hiện bằng các phương thức setter gọi container trên các bean của bạn sau khi gọi một hàm tạo không có đối số hoặc phương thức nhà máy tĩnh không đối số để khởi tạo bean của bạn.
public class Triangle{
private String type;
public String getType(){
return type;
}
public void setType(String type){ //setter injection
this.type = type;
}
}
<!-- setter injection -->
<bean id="triangle" class="com.test.dependencyInjection.Triangle">
<property name="type" value="equivialteral"/>
LƯU Ý: Đó là một quy tắc tốt để sử dụng các đối số của hàm tạo cho các phụ thuộc bắt buộc và setters cho các phụ thuộc tùy chọn. Lưu ý rằng nếu chúng ta sử dụng chú thích dựa trên chú thích @Required trên setter có thể được sử dụng để tạo setters như một phụ thuộc bắt buộc.
Điểm tương đồng tốt nhất tôi có thể nghĩ đến là bác sĩ phẫu thuật và trợ lý của anh ta trong một phòng phẫu thuật, trong đó bác sĩ phẫu thuật là người chính và trợ lý của anh ta cung cấp các thành phần phẫu thuật khác nhau khi anh ta cần để bác sĩ phẫu thuật có thể tập trung vào một điều anh ấy làm tốt nhất (phẫu thuật). Không có trợ lý, bác sĩ phẫu thuật phải tự lấy các bộ phận mỗi khi anh ta cần.
DI viết tắt, là một kỹ thuật để loại bỏ một trách nhiệm bổ sung chung (gánh nặng) đối với các thành phần để lấy các thành phần phụ thuộc, bằng cách cung cấp chúng cho nó.
DI đưa bạn đến gần hơn với nguyên tắc Trách nhiệm đơn (SR), như surgeon who can concentrate on surgery
.
Khi nào nên sử dụng DI: Tôi sẽ khuyên bạn nên sử dụng DI trong hầu hết các dự án sản xuất (nhỏ / lớn), đặc biệt là trong môi trường kinh doanh luôn thay đổi :)
Tại sao: Bởi vì bạn muốn mã của mình có thể dễ dàng kiểm tra, có thể giả được, v.v ... để bạn có thể nhanh chóng kiểm tra các thay đổi của mình và đẩy nó ra thị trường. Bên cạnh đó tại sao bạn lại không khi bạn có rất nhiều công cụ / khung công tác miễn phí tuyệt vời để hỗ trợ bạn trong hành trình đến một cơ sở mã nơi bạn có nhiều quyền kiểm soát hơn.
Ví dụ, chúng tôi có 2 lớp Client
và Service
. Client
sẽ sử dụngService
public class Service {
public void doSomeThingInService() {
// ...
}
}
Cách 1)
public class Client {
public void doSomeThingInClient() {
Service service = new Service();
service.doSomeThingInService();
}
}
Cách 2)
public class Client {
Service service = new Service();
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
Cách 3)
public class Client {
Service service;
public Client() {
service = new Service();
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
1) 2) 3) Sử dụng
Client client = new Client();
client.doSomeThingInService();
Ưu điểm
Nhược điểm
Client
lớp kiểm traService
tạo, chúng ta cần thay đổi mã ở mọi nơi tạo Service
đối tượngCách 1) Xây dựng tiêm
public class Client {
Service service;
Client(Service service) {
this.service = service;
}
// Example Client has 2 dependency
// Client(Service service, IDatabas database) {
// this.service = service;
// this.database = database;
// }
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
Sử dụng
Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();
Cách 2) Tiêm thuốc
public class Client {
Service service;
public void setService(Service service) {
this.service = service;
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
Sử dụng
Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();
Cách 3) Giao diện tiêm
Kiểm tra https://en.wikipedia.org/wiki/Dependency_injection
===
Bây giờ, mã này đã được theo dõi Dependency Injection
và nó dễ dàng hơn cho Client
lớp thử nghiệm .
Tuy nhiên, chúng tôi vẫn sử dụng new Service()
nhiều thời gian và nó không tốt khi thay đổi hàm Service
tạo. Để ngăn chặn điều đó, chúng ta có thể sử dụng kim phun DI như
1) Hướng dẫn đơn giảnInjector
public class Injector {
public static Service provideService(){
return new Service();
}
public static IDatabase provideDatatBase(){
return new SqliteDatabase();
}
public static ObjectA provideObjectA(){
return new ObjectA(provideService(...));
}
}
Sử dụng
Service service = Injector.provideService();
2) Sử dụng thư viện: Dành cho Android dagger2
Ưu điểm
Service
, bạn chỉ cần thay đổi nó trong lớp InjectionConstructor Injection
, khi bạn nhìn vào constructor của Client
, bạn sẽ thấy có bao nhiêu sự phụ thuộc của Client
lớpNhược điểm
Constructor Injection
, Service
đối tượng được tạo khi Client
được tạo, đôi khi chúng ta sử dụng hàm trong Client
lớp mà không sử dụng Service
nên đã tạo Service
bị lãng phíhttps://en.wikipedia.org/wiki/Dependency_injection
Một phụ thuộc là một đối tượng có thể được sử dụng (
Service
)
Một phép tiêm là việc truyền một phụ thuộc (Service
) đến một đối tượng phụ thuộc (Client
) sẽ sử dụng nó
Điều đó có nghĩa là các đối tượng chỉ nên có nhiều phụ thuộc cần thiết để thực hiện công việc của họ và số lượng phụ thuộc nên ít. Hơn nữa, các phụ thuộc của một đối tượng nên nằm trên các giao diện chứ không phải trên các đối tượng cụ thể của Wap, khi có thể. (Một đối tượng cụ thể là bất kỳ đối tượng nào được tạo bằng từ khóa mới.) Khớp nối lỏng lẻo thúc đẩy khả năng sử dụng lại nhiều hơn, khả năng bảo trì dễ dàng hơn và cho phép bạn dễ dàng cung cấp các đối tượng giả mạo thay thế cho các dịch vụ đắt tiền.
Công cụ phụ thuộc vào tiêm chích (DI) còn được gọi là Chuyển đổi điều khiển (IoC), có thể được sử dụng như một kỹ thuật để khuyến khích khớp nối lỏng lẻo này.
Có hai cách tiếp cận chính để thực hiện DI:
Đó là kỹ thuật chuyển các phụ thuộc đối tượng đến hàm tạo của nó.
Lưu ý rằng hàm tạo chấp nhận một giao diện và không phải đối tượng cụ thể. Ngoài ra, lưu ý rằng một ngoại lệ được đưa ra nếu tham số orderDao là null. Điều này nhấn mạnh tầm quan trọng của việc nhận được một phụ thuộc hợp lệ. Theo tôi, Con constructor tiêm là cơ chế ưa thích để cung cấp cho một đối tượng các phụ thuộc của nó. Nhà phát triển đã rõ ràng trong khi gọi đối tượng cần phải cung cấp các đối tượng phụ thuộc cho đối tượng của Person Person để thực hiện đúng.
Nhưng hãy xem xét ví dụ sau đây Giả sử bạn có một lớp với mười phương thức không có phụ thuộc, nhưng bạn đang thêm một phương thức mới có phụ thuộc vào IDAO. Bạn có thể thay đổi hàm tạo để sử dụng Trình xây dựng, nhưng điều này có thể buộc bạn thay đổi tất cả các lệnh gọi hàm tạo ở mọi nơi. Ngoài ra, bạn chỉ có thể thêm một hàm tạo mới có phụ thuộc, nhưng sau đó làm thế nào để nhà phát triển dễ dàng biết khi nào nên sử dụng một hàm tạo khác. Cuối cùng, nếu phụ thuộc rất tốn kém để tạo, tại sao nó phải được tạo và chuyển cho nhà xây dựng khi nó chỉ có thể được sử dụng hiếm khi? Tiêm Setter tiêm chích là một kỹ thuật DI khác có thể được sử dụng trong các tình huống như thế này.
Setter tiêm không buộc các phụ thuộc phải được chuyển đến các nhà xây dựng. Thay vào đó, các phụ thuộc được đặt vào các thuộc tính công khai được hiển thị bởi đối tượng cần. Như đã ngụ ý trước đây, các động lực chính để thực hiện việc này bao gồm:
Dưới đây là ví dụ về cách mã trên sẽ như thế nào:
public class Person {
public Person() {}
public IDAO Address {
set { addressdao = value; }
get {
if (addressdao == null)
throw new MemberAccessException("addressdao" +
" has not been initialized");
return addressdao;
}
}
public Address GetAddress() {
// ... code that uses the addressdao object
// to fetch address details from the datasource ...
}
// Should not be called directly;
// use the public property instead
private IDAO addressdao;
Tôi nghĩ vì mọi người đã viết cho DI, hãy để tôi hỏi một vài câu hỏi ..
Điều này dựa trên câu trả lời @Adam N được đăng.
Tại sao PersonService không còn phải lo lắng về GroupMembershipService? Bạn vừa đề cập GroupMembership có nhiều thứ (đối tượng / thuộc tính) mà nó phụ thuộc. Nếu GMService được yêu cầu trong PService, bạn sẽ có nó như một tài sản. Bạn có thể chế giễu điều đó bất kể bạn có tiêm hay không. Lần duy nhất tôi muốn được tiêm là nếu GMService có các lớp con cụ thể hơn, mà bạn sẽ không biết cho đến khi chạy. Sau đó, bạn muốn tiêm lớp con. Hoặc nếu bạn muốn sử dụng nó như là đơn hoặc nguyên mẫu. Thành thật mà nói, tệp cấu hình có mọi thứ được mã hóa cứng như lớp con cho một loại (giao diện) mà nó sẽ tiêm trong thời gian biên dịch.
BIÊN TẬP
Một bình luận tốt đẹp của Jose Maria Arranz trên DI
DI làm tăng sự gắn kết bằng cách loại bỏ bất kỳ nhu cầu nào để xác định hướng phụ thuộc và viết bất kỳ mã keo nào.
Sai. Hướng của các phụ thuộc là ở dạng XML hoặc dưới dạng các chú thích, các phụ thuộc của bạn được viết dưới dạng mã XML và các chú thích. XML và chú thích là mã nguồn.
DI giảm khớp nối bằng cách làm cho tất cả các thành phần của bạn được mô đun hóa (tức là có thể thay thế) và có các giao diện được xác định rõ với nhau.
Sai. Bạn không cần khung DI để xây dựng mã mô-đun dựa trên giao diện.
Về thay thế: với kho lưu trữ .properies rất đơn giản và Class.forName bạn có thể xác định lớp nào có thể thay đổi. Nếu BẤT K class lớp mã nào của bạn có thể thay đổi, Java không dành cho bạn, hãy sử dụng ngôn ngữ kịch bản. Nhân tiện: chú thích không thể thay đổi mà không cần biên dịch lại.
Theo tôi có một lý do duy nhất cho khung DI: giảm tấm nồi hơi. Với một hệ thống nhà máy được thực hiện tốt, bạn có thể thực hiện tương tự, được kiểm soát nhiều hơn và dễ dự đoán hơn như khung DI ưa thích của bạn, khung DI hứa sẽ giảm mã (XML và chú thích cũng là mã nguồn). Vấn đề là việc giảm tấm nồi hơi này chỉ thực sự trong các trường hợp rất đơn giản (một thể hiện trên mỗi lớp và tương tự), đôi khi trong thế giới thực, việc chọn đối tượng dịch vụ chiếm dụng không dễ dàng như ánh xạ một lớp tới một đối tượng đơn lẻ.
Các câu trả lời phổ biến là không có ích, bởi vì chúng xác định tiêm phụ thuộc theo cách không hữu ích. Chúng ta hãy đồng ý rằng bằng "sự phụ thuộc", chúng tôi muốn nói đến một số đối tượng khác đã tồn tại trước mà đối tượng X của chúng ta cần. Nhưng chúng tôi không nói rằng chúng tôi đang thực hiện "tiêm phụ thuộc" khi chúng tôi nói
$foo = Foo->new($bar);
Chúng ta chỉ cần gọi các tham số truyền vào hàm tạo. Chúng tôi đã làm điều đó thường xuyên kể từ khi các nhà xây dựng được phát minh.
"Tiêm phụ thuộc" được coi là một loại "đảo ngược kiểm soát", có nghĩa là một số logic được đưa ra khỏi người gọi. Đó không phải là trường hợp khi người gọi chuyển các tham số, vì vậy nếu đó là DI, DI sẽ không bao hàm sự đảo ngược của điều khiển.
DI có nghĩa là có một mức trung gian giữa người gọi và nhà xây dựng quản lý các phụ thuộc. Makefile là một ví dụ đơn giản về tiêm phụ thuộc. "Người gọi" là người gõ "make bar" trên dòng lệnh và "constructor" là trình biên dịch. Makefile chỉ định rằng thanh phụ thuộc vào foo và nó thực hiện một
gcc -c foo.cpp; gcc -c bar.cpp
trước khi làm một
gcc foo.o bar.o -o bar
Người gõ "make bar" không cần biết thanh đó phụ thuộc vào foo. Sự phụ thuộc đã được đưa vào giữa "make bar" và gcc.
Mục đích chính của cấp độ trung gian không chỉ là truyền các phụ thuộc cho nhà xây dựng, mà là liệt kê tất cả các phụ thuộc ở một nơi và để ẩn chúng khỏi bộ mã hóa (không phải để bộ mã hóa cung cấp chúng).
Thông thường cấp trung gian cung cấp các nhà máy cho các đối tượng được xây dựng, phải cung cấp một vai trò mà mỗi loại đối tượng được yêu cầu phải đáp ứng. Đó là bởi vì có một mức độ trung gian che giấu các chi tiết xây dựng, bạn đã phải chịu hình phạt trừu tượng được áp đặt bởi các nhà máy, vì vậy bạn cũng có thể sử dụng các nhà máy.
Dependency Injection có nghĩa là một cách (thực ra là bất kỳ cách nào ) để một phần của mã (ví dụ: một lớp) có quyền truy cập vào các phụ thuộc (các phần khác của mã, ví dụ như các lớp khác, nó phụ thuộc vào) theo cách mô đun mà không bị mã hóa (vì vậy chúng có thể thay đổi hoặc được ghi đè một cách tự do, hoặc thậm chí được tải vào lúc khác, khi cần thiết)
(và ps, vâng, nó đã trở thành một cái tên 25 đô la được thổi phồng quá mức cho một khái niệm khá đơn giản) , .25
xu của tôi
Tôi biết đã có nhiều câu trả lời, nhưng tôi thấy điều này rất hữu ích: http://tutorials.jenkov.com/dependency-injection/index.html
public class MyDao {
protected DataSource dataSource = new DataSourceImpl(
"driver", "url", "user", "password");
//data access methods...
public Person readPerson(int primaryKey) {...}
}
public class MyDao {
protected DataSource dataSource = null;
public MyDao(String driver, String url, String user, String password) {
this.dataSource = new DataSourceImpl(driver, url, user, password);
}
//data access methods...
public Person readPerson(int primaryKey) {...}
}
Lưu ý cách DataSourceImpl
khởi tạo được chuyển vào một hàm tạo. Hàm tạo có bốn tham số là bốn giá trị cần thiết cho DataSourceImpl
. Mặc dù MyDao
lớp vẫn phụ thuộc vào bốn giá trị này, nhưng nó không còn thỏa mãn các phụ thuộc này nữa. Chúng được cung cấp bởi bất kỳ lớp nào tạo ra một MyDao
thể hiện.
Tiêm phụ thuộc là một giải pháp khả thi cho những gì thường được gọi là yêu cầu "Phụ thuộc Obfuscation". Sự phụ thuộc Obfuscation là một phương pháp đưa bản chất 'rõ ràng' ra khỏi quá trình cung cấp một sự phụ thuộc cho một lớp học đòi hỏi nó và do đó, che giấu, theo một cách nào đó, việc cung cấp sự phụ thuộc đã nói vào lớp nói trên. Điều này không nhất thiết phải là một điều xấu. Trong thực tế, bằng cách làm xáo trộn cách thức cung cấp một phụ thuộc cho một lớp thì một cái gì đó bên ngoài lớp chịu trách nhiệm tạo ra sự phụ thuộc có nghĩa là, trong các tình huống khác nhau, việc thực hiện phụ thuộc khác nhau có thể được cung cấp cho lớp mà không thực hiện bất kỳ thay đổi nào đến lớp. Điều này rất tốt cho việc chuyển đổi giữa chế độ sản xuất và thử nghiệm (ví dụ: sử dụng phụ thuộc dịch vụ 'giả').
Thật không may, điều tồi tệ là một số người đã cho rằng bạn cần một khung chuyên biệt để thực hiện việc ẩn phụ thuộc và bạn bằng cách nào đó là một lập trình viên 'ít hơn' nếu bạn chọn không sử dụng một khung cụ thể để làm điều đó. Một huyền thoại khác, cực kỳ đáng lo ngại, được nhiều người tin rằng, tiêm phụ thuộc là cách duy nhất để đạt được sự phụ thuộc vào obfuscation. Điều này là sai lầm và lịch sử và rõ ràng là sai 100% nhưng bạn sẽ gặp khó khăn trong việc thuyết phục một số người rằng có những lựa chọn thay thế cho việc tiêm phụ thuộc cho các yêu cầu che giấu phụ thuộc của bạn.
Các lập trình viên đã hiểu được yêu cầu che giấu phụ thuộc trong nhiều năm và nhiều giải pháp thay thế đã phát triển cả trước và sau khi tiêm phụ thuộc được hình thành. Có các mẫu Factory nhưng cũng có nhiều tùy chọn sử dụng ThreadLocal trong đó không cần tiêm vào một trường hợp cụ thể nào - sự phụ thuộc được đưa vào luồng một cách hiệu quả có lợi cho việc làm cho đối tượng có sẵn (thông qua các phương thức getter tĩnh tiện lợi) cho bất kỳlớp yêu cầu nó mà không phải thêm chú thích vào các lớp yêu cầu nó và thiết lập 'keo' XML phức tạp để thực hiện. Khi sự phụ thuộc của bạn là cần thiết cho sự kiên trì (JPA / JDO hoặc bất cứ điều gì), nó cho phép bạn đạt được "sự kiên trì yên tĩnh" dễ dàng hơn nhiều và với mô hình miền và các lớp mô hình kinh doanh được tạo thành hoàn toàn từ POJO (nghĩa là không có khung cụ thể / bị khóa trong các chú thích).
Từ cuốn sách, Developer Nhà phát triển Java có căn cứ: Các kỹ thuật quan trọng của lập trình Java 7 và polyglot
DI là một hình thức cụ thể của IoC, theo đó quá trình tìm kiếm các phụ thuộc của bạn nằm ngoài sự kiểm soát trực tiếp của mã hiện đang thực thi của bạn.
Trước khi đi đến mô tả kỹ thuật, trước tiên hãy hình dung nó bằng một ví dụ thực tế bởi vì bạn sẽ tìm thấy rất nhiều công cụ kỹ thuật để học cách tiêm phụ thuộc nhưng thời gian tối đa những người như tôi không thể có được khái niệm cốt lõi về nó.
Trong bức ảnh đầu tiên, Giả sử rằng bạn có một nhà máy sản xuất ô tô với rất nhiều sự hợp nhất. Một chiếc xe thực sự được chế tạo trong đơn vị lắp ráp nhưng nó cần động cơ , ghế ngồi cũng như bánh xe . Vì vậy, đơn vị lắp ráp phụ thuộc vào tất cả các đơn vị này và chúng là sự phụ thuộc của nhà máy.
Bạn có thể cảm thấy rằng bây giờ quá phức tạp để duy trì tất cả các nhiệm vụ trong nhà máy này bởi vì cùng với nhiệm vụ chính (Lắp ráp xe trong đơn vị lắp ráp), bạn cũng phải tập trung vào các đơn vị khác . Bây giờ rất tốn kém để duy trì và xây dựng nhà máy là rất lớn vì vậy bạn phải mất thêm tiền để thuê.
Bây giờ, hãy nhìn vào bức tranh thứ hai. Nếu bạn tìm thấy một số công ty cung cấp sẽ cung cấp cho bạn bánh xe , ghế ngồi và động cơ rẻ hơn chi phí tự sản xuất thì bây giờ bạn không cần phải sản xuất chúng trong nhà máy của mình. Bạn có thể thuê một tòa nhà nhỏ hơn bây giờ chỉ cho đơn vị lắp ráp của bạn sẽ giảm bớt nhiệm vụ bảo trì của bạn và giảm chi phí thuê thêm của bạn. Bây giờ bạn cũng có thể chỉ tập trung vào nhiệm vụ chính của mình (Lắp ráp xe hơi).
Bây giờ chúng tôi có thể nói rằng tất cả các phụ thuộc để lắp ráp một chiếc xe được tiêm vào nhà máy từ các nhà cung cấp . Đây là một ví dụ về tiêm phụ thuộc ngoài đời thực (DI) .
Bây giờ trong từ kỹ thuật, tiêm phụ thuộc là một kỹ thuật trong đó một đối tượng (hoặc phương thức tĩnh) cung cấp các phụ thuộc của đối tượng khác. Vì vậy, chuyển giao nhiệm vụ tạo đối tượng cho người khác và trực tiếp sử dụng phụ thuộc được gọi là tiêm phụ thuộc.
Điều này sẽ giúp bạn bây giờ để học DI với một số từ thông minh. Điều này sẽ hiển thị khi nào nên sử dụng DI và khi nào không nên .
từ Apress Book.Spring.Persistence.with.Hibernate.Oct.2010
Mục đích của việc tiêm phụ thuộc là tách rời công việc giải quyết các thành phần phần mềm bên ngoài khỏi logic nghiệp vụ ứng dụng của bạn. Khi tiêm phụ thuộc, các chi tiết về cách một thành phần truy cập các dịch vụ cần thiết có thể được trộn lẫn với mã của thành phần. Điều này không chỉ làm tăng khả năng xảy ra lỗi, thêm sự phình to mã và phóng to sự phức tạp bảo trì; nó kết hợp các thành phần lại với nhau chặt chẽ hơn, gây khó khăn cho việc sửa đổi các phụ thuộc khi tái cấu trúc hoặc thử nghiệm.
Dependency Injection (DI) là một trong các Mẫu thiết kế, sử dụng tính năng cơ bản của OOP - mối quan hệ trong một đối tượng với một đối tượng khác. Trong khi kế thừa kế thừa một đối tượng để thực hiện một đối tượng khác phức tạp và cụ thể hơn, mối quan hệ hoặc liên kết chỉ đơn giản tạo một con trỏ đến một đối tượng khác từ một đối tượng sử dụng thuộc tính. Sức mạnh của DI kết hợp với các tính năng khác của OOP là giao diện và mã ẩn. Giả sử, chúng tôi có một khách hàng (người đăng ký) trong thư viện, họ chỉ có thể mượn một cuốn sách để đơn giản.
Giao diện của cuốn sách:
package com.deepam.hidden;
public interface BookInterface {
public BookInterface setHeight(int height);
public BookInterface setPages(int pages);
public int getHeight();
public int getPages();
public String toString();
}
Tiếp theo chúng ta có thể có nhiều loại sách; một trong những loại là hư cấu:
package com.deepam.hidden;
public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages
/** constructor */
public FictionBook() {
// TODO Auto-generated constructor stub
}
@Override
public FictionBook setHeight(int height) {
this.height = height;
return this;
}
@Override
public FictionBook setPages(int pages) {
this.pages = pages;
return this;
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return height;
}
@Override
public int getPages() {
// TODO Auto-generated method stub
return pages;
}
@Override
public String toString(){
return ("height: " + height + ", " + "pages: " + pages);
}
}
Bây giờ thuê bao có thể có liên kết đến cuốn sách:
package com.deepam.hidden;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Subscriber {
BookInterface book;
/** constructor*/
public Subscriber() {
// TODO Auto-generated constructor stub
}
// injection I
public void setBook(BookInterface book) {
this.book = book;
}
// injection II
public BookInterface setBook(String bookName) {
try {
Class<?> cl = Class.forName(bookName);
Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
BookInterface book = (BookInterface) constructor.newInstance();
//book = (BookInterface) Class.forName(bookName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return book;
}
public BookInterface getBook() {
return book;
}
public static void main(String[] args) {
}
}
Tất cả ba lớp có thể được ẩn để thực hiện riêng của nó. Bây giờ chúng ta có thể sử dụng mã này cho DI:
package com.deepam.implement;
import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;
public class CallHiddenImplBook {
public CallHiddenImplBook() {
// TODO Auto-generated constructor stub
}
public void doIt() {
Subscriber ab = new Subscriber();
// injection I
FictionBook bookI = new FictionBook();
bookI.setHeight(30); // cm
bookI.setPages(250);
ab.setBook(bookI); // inject
System.out.println("injection I " + ab.getBook().toString());
// injection II
FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
System.out.println("injection II " + ab.getBook().toString());
}
public static void main(String[] args) {
CallHiddenImplBook kh = new CallHiddenImplBook();
kh.doIt();
}
}
Có nhiều cách khác nhau để sử dụng tiêm phụ thuộc. Có thể kết hợp nó với Singleton, v.v., nhưng về cơ bản, nó chỉ được liên kết thực hiện bằng cách tạo thuộc tính của loại đối tượng bên trong một đối tượng khác. Tính hữu dụng là duy nhất và duy nhất trong tính năng, mã đó, mà chúng ta nên viết đi viết lại luôn được chuẩn bị và thực hiện cho chúng ta về phía trước. Đây là lý do tại sao DI liên kết chặt chẽ với Inversion of Control (IoC), điều đó có nghĩa là chương trình của chúng tôi vượt qua kiểm soát một mô-đun đang chạy khác, đó là việc tiêm đậu vào mã của chúng tôi. (Mỗi đối tượng, có thể được tiêm có thể được ký hoặc coi là Bean.) Ví dụ trong Spring, nó được thực hiện bằng cách tạo và khởi tạo ApplicationContextcontainer, mà làm việc này cho chúng tôi. Chúng tôi chỉ đơn giản trong mã của chúng tôi tạo ra Bối cảnh và gọi khởi tạo các bean. Trong thời điểm đó tiêm đã được thực hiện tự động.
Phụ thuộc tiêm cho trẻ 5 tuổi.
Khi bạn đi và lấy đồ ra khỏi tủ lạnh cho chính mình, bạn có thể gây ra vấn đề. Bạn có thể để cánh cửa mở, bạn có thể nhận được một cái gì đó Mẹ hoặc bố không muốn bạn có. Bạn thậm chí có thể đang tìm kiếm thứ gì đó chúng tôi thậm chí không có hoặc đã hết hạn.
Những gì bạn nên làm là nêu rõ một nhu cầu, "Tôi cần uống gì đó vào bữa trưa", và sau đó chúng tôi sẽ đảm bảo bạn có thứ gì đó khi bạn ngồi xuống ăn.
Từ Christoffer Noring, cuốn sách của Pablo Deeleman về Học tập góc cạnh - Ấn bản thứ hai:
"Khi các ứng dụng của chúng tôi phát triển và phát triển, mỗi một trong số các thực thể mã của chúng tôi sẽ yêu cầu các thể hiện của các đối tượng khác , được biết đến như là các phụ thuộc trong thế giới của công nghệ phần mềm. Hành động chuyển các phụ thuộc đó đến máy khách phụ thuộc được gọi là tiêm , và nó cũng đòi hỏi sự tham gia của một thực thể mã khác, được đặt tên là người tiêm . Người tiêm sẽ chịu trách nhiệm khởi tạo và khởi động các phụ thuộc cần thiếtvì vậy chúng sẵn sàng để sử dụng ngay từ khi chúng được tiêm thành công vào máy khách. Điều này rất quan trọng vì khách hàng không biết gì về cách và chỉ biết về giao diện họ thực hiện để sử dụng chúng. "khởi tạo các phụ thuộc của chính nó
Từ: Anton Moiseev. cuốn sách Phát triển góc cạnh với bản mô tả, ấn bản thứ hai.
Nói tóm lại, DI giúp bạn viết mã theo cách liên kết lỏng lẻo và làm cho mã của bạn dễ kiểm tra và tái sử dụng hơn .
Nói một cách đơn giản, tiêm phụ thuộc (DI) là cách để loại bỏ các phụ thuộc hoặc khớp nối chặt chẽ giữa các đối tượng khác nhau. Dependency Injection cung cấp một hành vi gắn kết cho từng đối tượng.
DI là việc triển khai hiệu trưởng IOC của Spring với nội dung "Đừng gọi cho chúng tôi, chúng tôi sẽ gọi cho bạn". Sử dụng lập trình tiêm phụ thuộc không cần tạo đối tượng bằng từ khóa mới.
Các đối tượng được tải một lần trong bộ chứa Spring và sau đó chúng tôi sử dụng lại chúng bất cứ khi nào chúng tôi cần bằng cách tìm nạp các đối tượng đó từ bộ chứa Spring bằng phương thức getBean (String beanName).
Việc tiêm phụ thuộc là trái tim của khái niệm liên quan đến Spring Framework. Trong khi tạo ra khuôn khổ của bất kỳ mùa xuân dự án nào cũng có thể thực hiện vai trò quan trọng và ở đây, việc tiêm phụ thuộc đến trong bình.
Trên thực tế, Giả sử trong java bạn đã tạo hai lớp khác nhau là lớp A và lớp B, và bất cứ chức năng nào có sẵn trong lớp B mà bạn muốn sử dụng trong lớp A, vì vậy có thể sử dụng phép tiêm phụ thuộc vào thời điểm đó. nơi bạn có thể đặt đối tượng của một lớp khác, giống như cách bạn có thể đưa toàn bộ một lớp vào một lớp khác để làm cho nó có thể truy cập được. bằng cách này phụ thuộc có thể được khắc phục.
KHAI THÁC PHỤ THUỘC LÀ ĐƠN GIẢN HÓA HAI LỚP VÀ VÀO LÚC CÙNG THỜI GIAN TUYỆT VỜI.