Tiêm phụ thuộc là gì?


3075

Đã 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?


Xem cuộc thảo luận của tôi về Tiêm phụ thuộc ở đây .
Kevin S.

33
Tôi đồng ý với các ý kiến ​​liên quan đến các liên kết. Tôi có thể hiểu bạn có thể muốn tham khảo người khác. Nhưng ít nhất hãy thêm lý do tại sao bạn liên kết chúng và điều gì làm cho liên kết này tốt hơn các liên kết khác mà tôi có thể nhận được bằng cách sử dụng google
Christian Payne

@AR: Về mặt kỹ thuật, Dependency Injection không phải là một dạng đặc biệt của IoC. Thay vào đó, IoC là một trong những kỹ thuật được sử dụng để cung cấp Dependency Injection. Các kỹ thuật khác có thể được sử dụng để cung cấp Dependency Injection (mặc dù IoC là công cụ duy nhất được sử dụng chung) và IoC cũng được sử dụng cho nhiều vấn đề khác.
Sean Reilly

Một trong những lời giải thích hay nhất tôi từng đọc về DI là từ Guice của Google (phát âm là nước trái cây) http://code.google.com.vn/p/google-guice/wiki/Motivation?tm=6
Raj

136
Về liên kết, hãy nhớ rằng chúng thường biến mất theo cách này hay cách khác. Ngày càng có nhiều liên kết chết trong các câu trả lời SO. Vì vậy, cho dù bài viết được liên kết tốt đến đâu, nó cũng không tốt chút nào nếu bạn không thể tìm thấy nó.
DOK

Câu trả lời:


1931

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 myObjectliê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 myObjectvà có thể chặn cuộc gọi của nhà máy.

Giải pháp thay thế :

  • Truyền myObjectvào như là một đối số cho các nhà xây dựng
public 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.

  • Một thay thế phổ biến là xác định một hàm tạo không làm gì . Tiêm phụ thuộc có thể được thực hiện thông qua setters. (h / t @MikeVella).
  • Martin Fowler ghi lại một giải pháp thay thế thứ ba (h / t @MarcDix), trong đó các lớp thực hiện rõ ràng một giao diện cho các lập trình viên phụ thuộc muốn đưa vào.

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.


25
Thừa nhận rằng bài viết giới thiệu của Martin Fowler của Ben Hoffstein là cần thiết vì chỉ ra một câu 'phải đọc' về chủ đề này, tôi chấp nhận câu trả lời của wds vì nó thực sự trả lời câu hỏi ở đây trên SO.
AR.

121
+1 để giải thích và tạo động lực: tạo ra các đối tượng mà một lớp phụ thuộc vào vấn đề của người khác . Một cách khác để nói rằng DI làm cho các lớp gắn kết hơn (chúng có ít trách nhiệm hơn).
Fuhrmanator

13
Bạn nói rằng sự phụ thuộc được truyền "vào nhà xây dựng" nhưng theo tôi hiểu thì điều này không hoàn toàn đúng. Vẫn còn tiêm phụ thuộc nếu phụ thuộc được đặt làm thuộc tính sau khi đối tượng đã được khởi tạo, đúng không?
Mike Vella

1
@MikeVella Vâng, đúng vậy. Nó không có sự khác biệt thực sự trong hầu hết các trường hợp, mặc dù các thuộc tính thường linh hoạt hơn một chút. Tôi sẽ chỉnh sửa văn bản một chút để chỉ ra điều đó.
ngày

2
Một trong những câu trả lời hay nhất mà tôi đã tìm thấy cho đến nay, do đó tôi thực sự quan tâm đến việc cải thiện nó. Nó thiếu một mô tả về hình thức tiêm phụ thuộc thứ ba: Giao diện tiêm .
Marc Dix

2351

Đị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ó. [...].

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.


35
Tôi thích phần giải thích bài báo của James, đặc biệt là phần cuối: "Tuy nhiên, bạn phải ngạc nhiên trước bất kỳ cách tiếp cận nào có ba khái niệm ('TripPlanner,' 'CabAgency,' và 'AirlineAgency'), biến chúng thành chín lớp, và sau đó thêm hàng chục dòng mã keo và cấu hình XML trước khi một dòng logic ứng dụng được viết. " Đây là những gì tôi đã thấy rất thường xuyên (đáng buồn thay) - rằng việc tiêm phụ thuộc (tốt theo cách giải thích của anh ta) bị lạm dụng để làm quá nhiều thứ có thể được thực hiện dễ dàng hơn - kết thúc bằng cách viết mã "hỗ trợ" ...
Matt

2
Re: "Khởi tạo và truyền đối tượng (phụ thuộc) một cách rõ ràng cũng giống như tiêm theo khung.". Vậy tại sao mọi người tạo ra các khung làm việc đó?
dzieciou

13
Vì lý do tương tự mà mọi khung công tác đều được viết (hoặc ít nhất nên lấy): bởi vì có rất nhiều mã lặp lại / soạn sẵn cần phải được viết khi bạn đạt đến một độ phức tạp nhất định. Vấn đề là nhiều lần mọi người sẽ đạt được một khuôn khổ ngay cả khi nó không thực sự cần thiết.
Thiago Arrais

14
$ 25 hạn cho một khái niệm 5 xu đã chết. Đây là một bài viết hay đã giúp tôi: codeproject.com/Articles/615139/,
Christine

@Matt các tệp cấu hình chỉ là "Trì hoãn quyết định" được đưa đến mức cực đoan - "Trì hoãn quyết định cho đến khi thực tế chạy". Theo tôi, Dagger và đặc biệt là Dagger đã tìm thấy điểm ngọt ngào "hoãn quyết định cho đến khi thời gian lắp ráp ứng dụng".
Thorbjørn Ravn Andersen

645

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 Carphụ 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ỉ Carnhà sản xuất mới có thể làm điều đó.

Vậy thì điều gì Dependency Injectionlà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 Wheelbất cứ khi nào chúng ta muốn. Ở đây, dependency( wheel) có thể được đưa vào Cartrong 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


20
Theo cách hiểu của tôi, thay vì khởi tạo một đối tượng mới như một phần của đối tượng khác, chúng ta có thể tiêm đối tượng đã nói khi nào và nếu cần, do đó loại bỏ sự phụ thuộc của đối tượng đầu tiên vào đối tượng đó. Có đúng không?
JeliBeanMachine

Tôi đã mô tả điều này với một ví dụ về cửa hàng cà phê ở đây: digigene.com/design-potypes/dependency-injection-coffeeshop
Ali Nem

11
Thực sự thích sự tương tự này bởi vì đó là tiếng Anh đơn giản sử dụng một sự tương tự đơn giản. Giả sử tôi là Toyota, đã trải qua quá nhiều về tài chính và nhân lực vào làm một chiếc xe hơi từ thiết kế đến lăn ra khỏi lắp ráp dòng, nếu có các nhà sản xuất lốp xe có uy tín hiện có, tại sao tôi nên bắt đầu từ đầu để làm cho một bộ phận sản xuất lốp nghĩa là để newmộ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
Jeb50

(không), .. thư viện / lớp bên ngoài hoặc thêm 2 từ DLL. Trừ khi chúng ta phải xem những gì bên trong lớp bên ngoài này, thêm nó vào DLL là một cách dễ dàng hơn. Vì vậy, tùy chọn 1 là cho newnó, 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.
Jeb50

1
@JeliBeanMchine phụ thuộc. Trước: Xe có sự phụ thuộc mã hóa vào NepaliRubberWheel. Sau: Xe có phụ thuộc vào bánh xe.
Mikael Ohlson

263

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::addManagerPersonService::removeManagersẽ 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 GroupMembershipServicetrong hàm tạo PersonServicevà 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ó GroupMembershipServicecó 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à PersonServicebây giờ không chỉ phụ thuộc vào GroupMembershipServicemà còn phụ thuộc vào mọi thứ khác. GroupMembershipServicephụ thuộc. Hơn nữa, liên kết đến GroupMembershipServiceđược mã hóa cứng PersonServicecó 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 GroupMembershipServicebên trong của bạn PersonService, bạn sẽ chuyển nó vào hàm PersonServicetạ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 PersonServicekhô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 GroupMembershipServicehoặc thực hiện GroupMembershipServicegiao diện đều có thể được "chèn" vào PersonServicePersonServicekhông cần biết về thay đổi.


29
Sẽ thật tuyệt nếu bạn có thể đưa ra ví dụ mã tương tự SAU khi sử dụng DI
CodyBugstein

170

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.


18
+1 "các đối tượng thay đổi thường xuyên hơn sau đó mã sử dụng chúng". Để khái quát hóa, thêm một hướng dẫn tại các điểm thông lượng. Tùy thuộc vào điểm thông lượng, các chỉ thị được gọi bằng các tên khác nhau !!
Chethan

139

Hãy thử ví dụ đơn giản với các lớp Xe hơiĐộ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.


2
Chỉ tò mò thôi, nó khác với mô hình chiến lược như thế nào? Mẫu này đang gói gọn các thuật toán và làm cho chúng có thể hoán đổi cho nhau. Nó cảm thấy như tiêm phụ thuộc và mô hình chiến lược rất giống nhau.
thuốc tiên

110

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.


59
Điểm nổi bật là, hãy tưởng tượng bạn thuê một thợ sửa ống nước để làm lại phòng tắm của bạn, người sau đó nói: "Tuyệt vời, đây là danh sách các công cụ và vật liệu tôi cần bạn có cho tôi". Không phải đó là công việc của thợ sửa ống nước sao?
jscs

Vì vậy, ai đó cần phải chăm sóc một số người mà nó không có kinh doanh biết .. nhưng vẫn quyết định thu thập danh sách thuyền, gậy và mồi - mặc dù đã sẵn sàng để sử dụng.
Chookoos

22
@JoshCaswell Không, đó sẽ là công việc của thợ sửa ống nước. Là một khách hàng, bạn cần hệ thống ống nước được thực hiện. Cho rằng bạn cần một thợ sửa ống nước. Thợ sửa ống nước cần công cụ của nó. Để có được những thứ đó, nó được trang bị bởi công ty ống nước. Là một khách hàng, bạn không muốn biết chính xác những gì thợ sửa ống nước làm hoặc cần. Là một thợ sửa ống nước bạn biết bạn cần gì, nhưng bạn chỉ muốn làm công việc của mình chứ không phải nhận mọi thứ. Là chủ nhân của thợ ống nước, bạn có trách nhiệm trang bị cho thợ ống nước những gì họ cần trước khi gửi chúng đến nhà của mọi người.
sara

@kai Tôi hiểu quan điểm của bạn. Trong phần mềm chúng ta đang nói về một nhà máy, đúng không? Nhưng DI cũng thường có nghĩa là lớp không sử dụng nhà máy vì vẫn chưa được tiêm. Bạn, khách hàng, sẽ cần liên hệ với chủ lao động (nhà máy) để cung cấp cho bạn các công cụ, để bạn có thể chuyển cho thợ sửa ống nước. Đó không phải là cách nó thực sự hoạt động trong một chương trình sao? Vì vậy, trong khi khách hàng (gọi lớp / chức năng / bất cứ điều gì) không phải mua các công cụ, họ vẫn phải là người trung gian đảm bảo họ đưa nó đến thợ sửa ống nước (lớp được tiêm) từ chủ nhân (nhà máy).
KingOf ALLTrades

1
@KingOf ALLTrades: Tất nhiên tại một số thời điểm, bạn phải có ai đó sử dụng và trang bị cho thợ ống nước, hoặc bạn không có thợ ống nước. Nhưng bạn không có khách hàng làm điều đó. Khách hàng chỉ yêu cầu một thợ sửa ống nước, và nhận được một người đã sẵn sàng với những gì anh ta cần để làm công việc của mình. Với DI, cuối cùng bạn vẫn có một số mã để thực hiện các phụ thuộc. Nhưng bạn đang tách nó ra khỏi mã thực sự hoạt động. Nếu bạn đưa nó đến mức tối đa, các đối tượng của bạn chỉ cần biết các phụ thuộc của chúng và việc xây dựng biểu đồ đối tượng xảy ra bên ngoài, thường là trong mã init.
cHao

102

Đây là lời giải thích đơn giản nhất về Dependency InjectionDependency Injection Container mà tôi từng thấy:

Không phụ thuộc tiêm

  • Ứng dụng cần Foo (ví dụ: bộ điều khiển), vì vậy:
  • Ứng dụng tạo Foo
  • Ứng dụng gọi Foo
    • Foo cần Bar (ví dụ: một dịch vụ), vì vậy:
    • Foo tạo Bar
    • Foo gọi cho Bar
      • Thanh cần Bim (một dịch vụ, một kho lưu trữ, trên mạng), vì vậy:
      • Bar tạo ra Bim
      • Bar làm một cái gì đó

Với tiêm phụ thuộc

  • Ứng dụng cần Foo, cần Bar, cần Bim, vì vậy:
  • Ứng dụng tạo Bim
  • Ứng dụng tạo Bar và cung cấp cho nó Bim
  • Ứng dụng tạo Foo và cung cấp cho nó Bar
  • Ứng dụng gọi Foo
    • Foo gọi cho Bar
      • Bar làm một cái gì đó

Sử dụng một container tiêm phụ thuộc

  • Ứng dụng cần Foo vì vậy:
  • Ứng dụng được Foo từ Container, vì vậy:
    • Container tạo Bim
    • Container tạo Bar và cung cấp cho nó Bim
    • Container tạo Foo và cung cấp cho nó Bar
  • Ứng dụng gọi Foo
    • Foo gọi cho Bar
      • Bar làm một cái gì đó

Phụ thuộc Tiêm phụ thuộcTiêm phụ thuộc là những thứ khác nhau:

  • Dependency Injection là một phương pháp để viết mã tốt hơn
  • DI Container là một công cụ để giúp tiêm phụ thuộc

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.



55

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(); 
    ... 
  } 
}

Chắc chắn trong phiên bản DI bạn sẽ không muốn khởi tạo đối tượng myDatabase trong hàm tạo không có đối số? Dường như không có điểm nào và sẽ đưa ra một ngoại lệ nếu bạn cố gọi DoStuff mà không gọi hàm tạo quá tải?
Matt Wilko

Chỉ khi new DatabaseThingie()không tạo ra một cá thể myDatabase hợp lệ.
Janeoodall

40

Để 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.

Không phụ thuộc tiêm

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();
}

Với tiêm phụ thuộc

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(); 
    ... 
  } 
}`

36

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 , DIlà 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à Dcủa SOLIDthầ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 classhoặ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ự Foophụ 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:

  • Nếu cần giữ lại quyền kiểm soát tuổi thọ của lớp phụ thuộc, lớp điều khiển có thể được thiết lập lại bằng cách đưa một nhà máy (trừu tượng) để tạo các thể hiện của lớp phụ thuộc vào lớp người tiêu dùng. Người tiêu dùng sẽ có thể có được các phiên bản thông qua một Createnhà 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.
  • Hoặc, kiểm soát tuổi thọ của các trường hợp phụ thuộc có thể được chuyển sang bộ chứa IoC (chi tiết hơn về điều này bên dưới).

Khi nào sử dụng DI?

  • Trường hợp có khả năng sẽ cần phải thay thế một phụ thuộc để thực hiện tương đương,
  • Bất cứ lúc nào bạn sẽ cần đơn vị kiểm tra các phương thức của một lớp để tách biệt các phụ thuộc của nó,
  • Trường hợp không chắc chắn về tuổi thọ của một phụ thuộc có thể đảm bảo thử nghiệm (ví dụ: Hey, MyDepClasslà 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 staticphụ thuộc vào hai lớp khác System.DateTimeSystem.Consolekhô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 DIPcho 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à MyLoggerchỉ 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 Consolemộ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 constructortiê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 Clocknhu cầu cụ thể cần được cung cấp, tất nhiên có thể hoàn nguyên DateTime.Nowvà 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, IoCcác thùng chứa cho phép xác định các mục sau:

  • ánh xạ giữa mỗi trừu tượng và triển khai cụ thể được cấu hình (ví dụ: "bất cứ khi nào người tiêu dùng yêu cầu IBar, trả lại một ConcreteBarthể hiện" )
  • các chính sách có thể được thiết lập để quản lý tuổi thọ của từng phụ thuộc, ví dụ: để tạo một đối tượng mới cho từng đối tượng người tiêu dùng, để chia sẻ một thể hiện phụ thuộc đơn lẻ trên tất cả người tiêu dùng, để chia sẻ cùng một đối tượng phụ thuộc trên cùng một luồng, v.v.
  • Trong .Net, các bộ chứa IoC nhận thức được các giao thức như IDisposablevà sẽ chịu trách nhiệm về các Disposingphụ 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 newphụ 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.


1
Vấn đề là DIP! = DI. DIP là về việc tách rời sự trừu tượng khỏi việc thực hiện: A. Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai nên phụ thuộc vào trừu tượng. B. Trừu tượng không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào trừu tượng. DI là một cách để tách rời việc tạo đối tượng khỏi việc sử dụng đối tượng.
Ricardo Rivaldo

Vâng, sự khác biệt được nêu rõ trong đoạn 2 của tôi, "DI một trong những triển khai có thể có của DIP" , trong mô hình RẮN của chú Bob. Tôi cũng đã nói rõ điều này trong một bài viết trước đó.
StuartLC

25

Toàn bộ quan điểm của Dependency Injection (DI) là giữ cho mã nguồn ứng dụng sạchổn định :

  • sạch mã khởi tạo phụ thuộc
  • ổn định bất kể phụ thuộc sử dụng

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.

Ví dụ: DI với shell script

Nếu bạn thỉnh thoảng làm việc bên ngoài Java, hãy nhớ lại cách sourcethường được sử dụng trong nhiều ngôn ngữ script (Shell, Tcl, v.v. hoặc thậm chí importtrong Python bị lạm dụng cho mục đích này).

Xem xét dependent.shkị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_fileskhông được xác định).

Bạn xác định archive_filestrong archive_files_zip.shtập lệnh thực hiện (sử dụng ziptrong 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_filesbằng cách sử dụng tarhoặc xz.

Ví dụ: loại bỏ DI

Nếu dependent.shtậ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".

Những từ cuối

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ề:

  • phát triển ứng dụng ( vòng đời phát hành mã nguồn đơn )
  • triển khai ứng dụng ( nhiều môi trường đích với vòng đời độc lập)

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ôi sẽ thêm khả năng hoàn thành một lớp cụ thể (thử nghiệm) mà không phải hoàn thành các phụ thuộc của nó, như là một mục đích cho DI.
David

22

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.

Máy ghi âm cầm tay reel-to-reel, giữa thế kỷ 20.

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.

  1. chúng ta có thể đặt cuộn dây bên trong máy
  2. chúng ta có thể cung cấp một cái móc cho cuộn nơi nó có thể được đặt.

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.

  • Độ kết dính cao và khớp nối lỏng lẻo.
  • Ngoại quan phụ thuộc và chỉ nhìn vào trách nhiệm.
  • Làm cho mọi thứ như các thành phần và kết hợp để tạo thành một hệ thống lớn với khả năng cao.
  • Nó giúp phát triển các thành phần chất lượng cao vì chúng được phát triển độc lập, chúng được kiểm tra đúng cách.
  • Nó giúp thay thế thành phần này bằng một thành phần khác nếu thất bại.

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

Sự khác biệt giữa nghịch đảo kiểm soát và tiêm phụ thuộc


17

Tiêm phụ thuộc là gì?

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ề

Cách thức hoạt động của Dependency Injection vào mùa xuân:

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ả.

Đảo ngược điều khiển (IOC)

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.

Hai loại tiêm phụ thuộc:

  1. Xây dựng tiêm
  2. Setter tiêm

1. Tiêm phụ thuộc dựa trên xây dựng:

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>

2. Tiêm phụ thuộc dựa trên Setter:

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.


15

Đ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.


@WindRider Cảm ơn. Tôi không thể đồng ý nhiều hơn. Cuộc sống con người và cơ thể con người là những ví dụ tuyệt vời về sự xuất sắc trong thiết kế .. cột sống là một ví dụ tuyệt vời về ESB :) ...
Anwar Husain

15

Ví dụ, chúng tôi có 2 lớp ClientService. Clientsẽ sử dụngService

public class Service {
    public void doSomeThingInService() {
        // ...
    }
}

Không phụ thuộc tiêm

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

  • Đơn giản

Nhược điểm

  • Khó cho Clientlớp kiểm tra
  • Khi chúng ta thay đổi hàm Servicetạo, chúng ta cần thay đổi mã ở mọi nơi tạo Serviceđối tượng

Sử dụng tiêm phụ thuộc

Cá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 Injectionvà nó dễ dàng hơn cho Clientlớ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 Servicetạ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

  • Làm bài kiểm tra dễ dàng hơn
  • Khi bạn thay đổi Service, bạn chỉ cần thay đổi nó trong lớp Injection
  • Nếu bạn sử dụng use Constructor 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 Clientlớp

Nhược điểm

  • Nếu bạn sử dụng sử dụng 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 Clientlớp mà không sử dụng Servicenên đã tạo Servicebị lãng phí

Định nghĩa tiêm phụ thuộc

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ó


13

Đ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:

  1. Xây dựng tiêm
  2. Tiêm thuốc

Xây dựng tiêm

Đó 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.

Setter tiêm

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:

  1. Hỗ trợ tiêm phụ thuộc mà không phải sửa đổi hàm tạo của lớp kế thừa.
  2. Cho phép các tài nguyên hoặc dịch vụ đắt tiền được tạo ra càng muộn càng tốt và chỉ khi cần thiết.

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;

3
Tôi nghĩ rằng đoạn đầu tiên của bạn đi lạc khỏi câu hỏi và hoàn toàn không phải là định nghĩa của DI (nghĩa là bạn đang cố gắng xác định RẮN, không phải DI). Về mặt kỹ thuật, ngay cả khi bạn có 100 phụ thuộc, bạn vẫn có thể sử dụng tiêm phụ thuộc. Tương tự, có thể tiêm phụ thuộc cụ thể - đó vẫn là tiêm phụ thuộc.
Jay Sullivan

10

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 ..

  1. Khi bạn có cấu hình DI trong đó tất cả các cài đặt thực tế (không phải giao diện) sẽ được đưa vào một lớp (ví dụ: dịch vụ cho bộ điều khiển) tại sao đó không phải là một loại mã hóa cứng?
  2. Nếu tôi muốn thay đổi đối tượng khi chạy thì sao? Ví dụ: cấu hình của tôi đã nói khi tôi khởi tạo MyContoder, tiêm cho FileLogger là ILogger. Nhưng tôi có thể muốn tiêm DatabaseLogger.
  3. Mỗi lần tôi muốn thay đổi những đối tượng mà AClass của tôi cần, bây giờ tôi cần xem xét hai vị trí - Bản thân lớp và tệp cấu hình. Làm thế nào mà làm cho cuộc sống dễ dàng hơn?
  4. Nếu Aproperty của AClass không được tiêm, việc chế nhạo nó có khó hơn không?
  5. Quay trở lại câu hỏi đầu tiên. Nếu sử dụng đối tượng mới () là xấu, tại sao chúng ta tiêm thực hiện và không phải giao diện? Tôi nghĩ rằng nhiều bạn đang nói rằng trên thực tế chúng ta đang tiêm giao diện nhưng cấu hình khiến bạn chỉ định việc thực hiện giao diện đó .. không phải lúc chạy .. nó bị mã hóa cứng trong thời gian biên dịch.

Đ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ẻ.


8

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.


8

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) , .25xu của tôi


8

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

Không phụ thuộc:

public class MyDao {

  protected DataSource dataSource = new DataSourceImpl(
    "driver", "url", "user", "password");

  //data access methods...
  public Person readPerson(int primaryKey) {...}     
}

Phụ thuộc:

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 DataSourceImplkhở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ù MyDaolớ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 MyDaothể hiện.


1
DI sẽ không vượt qua bạn bằng giao diện DataSourceImp của bạn đã được xây dựng?
PmanAce

6

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).



5

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độ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ất cả trong một nhà máy xe hơi.

Xưởng xe đơn giản


1
câu trả lời rõ ràng nhất trong số 40 ish. Ví dụ thực tế và hình ảnh. +1. Nên là câu trả lời được chấp nhận.
Marche Remi

4

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.


4

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.


4

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.


1
Đây rõ ràng là câu trả lời của phụ huynh. ;)
Marche Remi

4

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ạokhở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 của bạn dễ kiểm tratái sử dụng hơn .


3

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).


3

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.

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.