Hiểu tiêm phụ thuộc


109

Tôi đang đọc về tiêm phụ thuộc (DI). Đối với tôi, đó là một điều rất phức tạp để làm, vì tôi đang đọc nó đang đề cập đến sự đảo ngược của kiểm soát (IoC) và vì vậy tôi cảm thấy mình sẽ tham gia vào một hành trình.

Đây là sự hiểu biết của tôi: Thay vì tạo một mô hình trong lớp cũng tiêu thụ nó, bạn chuyển (tiêm) mô hình (đã chứa đầy các thuộc tính thú vị) đến nơi cần thiết (đến một lớp mới có thể lấy nó làm tham số trong người xây dựng).

Đối với tôi, đây chỉ là thông qua một cuộc tranh cãi. Tôi phải bỏ lỡ hiểu điểm? Có lẽ nó trở nên rõ ràng hơn với các dự án lớn hơn?

Hiểu biết của tôi là không DI (sử dụng mã giả):

public void Start()
{
    MyClass class = new MyClass();
}

...

public MyClass()
{
    this.MyInterface = new MyInterface(); 
}

Và DI sẽ là

public void Start()
{
    MyInterface myInterface = new MyInterface();
    MyClass class = new MyClass(myInterface);
}

...

public MyClass(MyInterface myInterface)
{
    this.MyInterface = myInterface; 
}

Ai đó có thể làm sáng tỏ một chút vì tôi chắc chắn rằng tôi đang ở trong một vũng bùn ở đây.


5
Hãy thử có 5 MyInterface và MyClass VÀ có nhiều lớp tạo thành chuỗi phụ thuộc. Nó trở thành một nỗi đau ở mông để thiết lập mọi thứ.
Euphoric

159
" Đối với tôi, đây chỉ là thông qua một cuộc tranh luận. Tôi có phải đã hiểu sai quan điểm? " Không. Bạn đã nhận nó. Đó là "tiêm phụ thuộc". Bây giờ hãy xem những thuật ngữ điên rồ nào khác mà bạn có thể đưa ra với các khái niệm đơn giản, thật thú vị!
Eric Lippert

8
Tôi không thể phản đối ý kiến ​​của ông Lippert đủ. Tôi cũng thấy nó là một thuật ngữ khó hiểu cho những gì tôi đang làm một cách tự nhiên.
Alex Humphrey

16
@EricLippert: Trong khi chính xác, tôi nghĩ rằng điều đó hơi giảm. Tham số-as-DI không phải là một khái niệm đơn giản ngay lập tức cho lập trình viên OO / mệnh lệnh trung bình của bạn, người đã từng truyền dữ liệu xung quanh. DI ở đây là cho phép bạn vượt qua hành vi xung quanh, đòi hỏi một thế giới quan linh hoạt hơn so với một lập trình viên tầm thường sẽ có.
Phoshi

14
@Phoshi: Bạn đã đưa ra một quan điểm tốt, nhưng bạn cũng đã đặt ngón tay của mình vào lý do tại sao tôi nghi ngờ rằng "tiêm phụ thuộc" là một ý tưởng tốt ngay từ đầu. Nếu tôi viết một lớp phụ thuộc vào tính chính xác và hiệu suất của nó đối với hành vi của lớp khác thì điều cuối cùng tôi muốn làm là khiến người dùng của lớp chịu trách nhiệm xây dựng và định cấu hình chính xác phụ thuộc! Đó là công việc của tôi . Mỗi khi bạn để người tiêu dùng tiêm một phụ thuộc, bạn sẽ tạo ra một điểm mà người tiêu dùng có thể làm sai.
Eric Lippert

Câu trả lời:


106

Vâng, bạn tiêm phụ thuộc của bạn, thông qua một nhà xây dựng hoặc thông qua các thuộc tính.
Một trong những lý do cho điều này, là không mã hóa MyClass với các chi tiết về cách một thể hiện của MyInterface cần được xây dựng. MyInterface có thể là một cái gì đó có toàn bộ danh sách các phụ thuộc và mã của MyClass sẽ trở nên xấu rất nhanh nếu bạn khởi tạo tất cả các phụ thuộc MyInterface bên trong MyClass.

Một lý do khác là để thử nghiệm.
Nếu bạn có sự phụ thuộc vào giao diện trình đọc tệp và bạn đưa sự phụ thuộc này thông qua một hàm tạo trong ConsumerClass, điều đó có nghĩa là trong quá trình kiểm tra, bạn có thể chuyển việc triển khai trình đọc tệp trong ConsumerClass trong bộ nhớ cho người tiêu dùng, tránh việc phải làm I / O trong quá trình thử nghiệm.


13
Điều này là tuyệt vời, giải thích tốt. Vui lòng chấp nhận +1 tưởng tượng vì đại diện của tôi sẽ không cho phép điều đó!
MyDaftQuestions

11
Kịch bản xây dựng phức tạp là một ứng cử viên tốt để sử dụng mô hình Nhà máy. Theo cách đó, nhà máy đảm nhận trách nhiệm xây dựng đối tượng, để bản thân lớp không phải làm và bạn tuân thủ nguyên tắc trách nhiệm duy nhất.
Matt Gibson

1
@MattGibson điểm tốt.
Stefan Billiet

2
+1 để kiểm tra, DI có cấu trúc tốt sẽ giúp cả tăng tốc kiểm tra và thực hiện kiểm tra đơn vị có trong lớp bạn đang kiểm tra.
Umur Kontacı

52

Làm thế nào DI có thể được thực hiện phụ thuộc rất nhiều vào ngôn ngữ được sử dụng.

Đây là một ví dụ đơn giản về DI:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo() {
        bar = new Bar();
        qux = new Qux();
    }
}

Điều này thật tệ, ví dụ như khi thử nghiệm tôi muốn sử dụng một đối tượng giả bar. Vì vậy, chúng ta có thể làm cho điều này linh hoạt hơn và cho phép các thể hiện được truyền qua hàm tạo:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo(Bar bar, Qux qux) {
        this.bar = bar;
        this.qux = qux;
    }
}

// in production:
new Foo(new Bar(), new Qux());
// in test:
new Foo(new BarMock(), new Qux());

Đây đã là hình thức tiêm phụ thuộc đơn giản nhất. Nhưng điều này vẫn tệ bởi vì mọi thứ phải được thực hiện thủ công (cũng bởi vì người gọi có thể giữ một tham chiếu đến các đối tượng bên trong của chúng tôi và do đó làm mất hiệu lực trạng thái của chúng tôi).

Chúng tôi có thể giới thiệu trừu tượng hơn bằng cách sử dụng các nhà máy:

  • Một tùy chọn là Foođược tạo bởi một nhà máy trừu tượng

    interface FooFactory {
        public Foo makeFoo();
    }
    
    class ProductionFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new Bar(), new Baz()) }
    }
    
    class TestFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new BarMock(), new Baz()) }
    }
    
    FooFactory fac = ...; // depends on test or production
    Foo foo = fac.makeFoo();
  • Một tùy chọn khác là chuyển một nhà máy đến nhà xây dựng:

    interface DependencyManager {
        public Bar makeBar();
        public Qux makeQux();
    }
    class ProductionDM implements DependencyManager {
        public Bar makeBar() { return new Bar() }
        public Qux makeQux() { return new Qux() }
    }
    class TestDM implements DependencyManager {
        public Bar makeBar() { return new BarMock() }
        public Qux makeQux() { return new Qux() }
    }
    
    class Foo {
        private Bar bar;
        private Qux qux;
    
        public Foo(DependencyManager dm) {
            bar = dm.makeBar();
            qux = dm.makeQux();
        }
    }

Vấn đề còn lại với điều này là chúng ta cần phải viết một DependencyManagerlớp con mới cho mỗi cấu hình và số lượng phụ thuộc có thể được quản lý khá hạn chế (mỗi phụ thuộc mới cần một phương thức mới trong giao diện).

Với các tính năng như phản chiếu và tải lớp động, chúng ta có thể phá vỡ điều này. Nhưng điều này phụ thuộc rất nhiều vào ngôn ngữ được sử dụng. Trong Perl, các lớp có thể được tham chiếu theo tên của chúng và tôi chỉ có thể làm

package Foo {
    use signatures;

    sub new($class, $dm) {
        return bless {
            bar => $dm->{bar}->new,
            qux => $dm->{qux}->new,
        } => $class;
    }
}

my $prod = { bar => 'My::Bar', qux => 'My::Qux' };
my $test = { bar => 'BarMock', qux => 'QuxMock' };
$test->{bar} = 'OtherBarMock';  # change conf at runtime

my $foo = Foo->new(rand > 0.5 ? $prod : $test);

Trong các ngôn ngữ như Java, tôi có thể có trình quản lý phụ thuộc hoạt động tương tự như Map<Class, Object>:

Bar bar = dm.make(Bar.class);

Lớp thực tế nào Bar.classđược phân giải có thể được cấu hình trong thời gian chạy, ví dụ: bằng cách duy trì Map<Class, Class>giao diện ánh xạ tới các cài đặt.

Map<Class, Class> dependencies = ...;

public <T> T make(Class<T> c) throws ... {
    // plus a lot more error checking...
    return dependencies.get(c).newInstance();
}

Vẫn còn một yếu tố thủ công liên quan đến việc viết hàm tạo. Nhưng chúng ta có thể làm cho hàm tạo hoàn toàn không cần thiết, ví dụ: bằng cách điều khiển DI thông qua các chú thích:

class Foo {
    @Inject(Bar.class)
    private Bar bar;

    @Inject(Qux.class)
    private Qux qux;

    ...
}

dm.make(Foo.class);  // takes care of initializing "bar" and "qux"

Dưới đây là một ví dụ triển khai của khung DI nhỏ (và rất hạn chế): http://ideone.com/b2ubuF , mặc dù việc triển khai này hoàn toàn không sử dụng được cho các đối tượng bất biến (việc triển khai ngây thơ này không thể lấy bất kỳ tham số nào cho hàm tạo).


7
Đây là siêu với các ví dụ tuyệt vời, mặc dù tôi sẽ mất vài lần đọc nó để tiêu hóa tất cả, nhưng cảm ơn vì đã dành quá nhiều thời gian.
MyDaftQuestions

33
Thật thú vị khi mỗi lần đơn giản hóa tiếp theo lại phức tạp hơn
Kromster

48

Bạn bè của chúng tôi trên Stack Overflow có một câu trả lời hay cho việc này . Yêu thích của tôi là câu trả lời thứ hai, bao gồm cả trích dẫn:

"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ó. (...).

từ blog của James Shore . Tất nhiên, có nhiều phiên bản / mẫu phức tạp hơn nằm trên lớp này, nhưng về cơ bản là đủ để hiểu những gì đang diễn ra. Thay vì một đối tượng tạo các biến đối tượng của riêng nó, chúng được truyền vào từ bên ngoài.


10
Tôi đã đọc điều này ... và cho đến bây giờ, nó không có ý nghĩa gì ... Bây giờ, nó có ý nghĩa. Sự phụ thuộc thực sự không phải là một khái niệm lớn để hiểu (bất kể nó mạnh đến mức nào) nhưng nó có một tên tuổi lớn và rất nhiều tiếng ồn trên internet liên quan đến nó!
MyDaftQuestions

18

Giả sử bạn đang thực hiện thiết kế nội thất trong phòng khách của mình: bạn lắp một chiếc đèn chùm lạ mắt trên trần nhà và cắm một chiếc đèn sàn phù hợp sang trọng. Một năm sau, người vợ đáng yêu của bạn quyết định cô ấy thích căn phòng ít trang trọng hơn một chút. Những vật cố ánh sáng sẽ dễ dàng thay đổi?

Ý tưởng đằng sau DI là sử dụng phương pháp "plug-in" ở mọi nơi. Điều này có vẻ không phải là một vấn đề lớn nếu bạn sống trong một lán đơn giản không có vách thạch cao và tất cả các dây điện tiếp xúc. (Bạn là người siêng năng - bạn có thể thay đổi bất cứ điều gì!) Và tương tự, trên một ứng dụng nhỏ & đơn giản, DI có thể thêm nhiều sự phức tạp hơn giá trị của nó.

Nhưng đối với một ứng dụng lớn và phức tạp, DI là không thể thiếu để bảo trì lâu dài. Nó cho phép bạn "rút phích cắm" toàn bộ các mô-đun khỏi mã của bạn (ví dụ: phụ trợ cơ sở dữ liệu) và trao đổi chúng với các mô-đun khác nhau thực hiện cùng một việc nhưng làm theo cách hoàn toàn khác (ví dụ: hệ thống lưu trữ đám mây).

BTW, một cuộc thảo luận về DI sẽ không đầy đủ nếu không có khuyến nghị về Dependency Injection trong .NET , bởi Mark Seeman. Nếu bạn đã quen thuộc với .NET và bạn từng có ý định tham gia vào phát triển SW quy mô lớn, thì đây là một bài đọc thiết yếu. Ông giải thích khái niệm tốt hơn nhiều so với tôi có thể.

Hãy để tôi để lại cho bạn một đặc điểm thiết yếu cuối cùng của DI mà ví dụ mã của bạn bỏ qua. DI cho phép bạn khai thác tiềm năng linh hoạt to lớn được gói gọn trong câu ngạn ngữ " Chương trình giao diện ". Để sửa đổi ví dụ của bạn một chút:

public void Main()
{
    ILightFixture fixture = new ClassyChandelier();
    MyRoom room = new MyRoom (fixture);
}
...
public MyRoom(ILightFixture fixture)
{
    this.MyLightFixture = fixture ; 
}

Nhưng NGAY BÂY GIỜ, vì MyRoomđược thiết kế cho giao diện ILightFixture , tôi có thể dễ dàng đi vào năm tới và thay đổi một dòng trong chức năng Chính ("tiêm" một "phụ thuộc" khác) và tôi ngay lập tức nhận được chức năng mới trong MyRoom (mà không phải xây dựng lại hoặc triển khai lại MyRoom, nếu nó tình cờ ở trong một thư viện riêng. Tất nhiên, điều này giả định rằng tất cả các triển khai ILightFixture của bạn được thiết kế với Liskov Thay thế trong tâm trí). Tất cả, chỉ với một thay đổi đơn giản:

public void Main()
{
    ILightFixture fixture = new MuchLessFormalLightFixture();
    MyRoom room = new MyRoom (fixture);
}

Ngoài ra, giờ đây tôi có thể Kiểm tra đơn vị MyRoom(bạn đang kiểm tra đơn vị, phải không?) Và sử dụng vật cố ánh sáng "giả", cho phép tôi kiểm tra MyRoomhoàn toàn độc lập vớiClassyChandelier.

Có nhiều lợi thế hơn, tất nhiên, nhưng đây là những người đã bán ý tưởng cho tôi.


Tôi muốn nói lời cảm ơn, điều này hữu ích với tôi, nhưng họ không muốn bạn làm điều đó, mà là sử dụng các bình luận để đề xuất cải tiến, vì vậy, nếu bạn cảm thấy thích nó, bạn có thể thêm một trong những lợi thế khác mà bạn đã đề cập. Nhưng nếu không, cảm ơn, điều này rất hữu ích cho tôi.
Steve

2
Tôi cũng quan tâm đến cuốn sách mà bạn đã tham khảo, nhưng tôi thấy đáng báo động rằng có một cuốn sách 584 trang về chủ đề này.
Steve

4

Việc tiêm phụ thuộc chỉ là truyền một tham số, nhưng điều đó vẫn dẫn đến một số vấn đề và tên được dự định tập trung một chút vào lý do tại sao sự khác biệt giữa việc tạo một đối tượng so với việc truyền một đối tượng là quan trọng.

Điều này quan trọng bởi vì (khá rõ ràng) bất kỳ lúc nào đối tượng A gọi loại B, A phụ thuộc vào cách B hoạt động. Điều đó có nghĩa là nếu A đưa ra quyết định cụ thể loại B nào sẽ sử dụng, thì rất nhiều tính linh hoạt trong cách A có thể được sử dụng bởi các lớp bên ngoài, mà không sửa đổi A, đã bị mất.

Tôi đề cập đến điều này bởi vì bạn nói về "thiếu điểm" của tiêm phụ thuộc, và đây là phần lớn lý do tại sao mọi người thích làm điều đó. Nó có thể chỉ có nghĩa là truyền một tham số, nhưng cho dù bạn làm như vậy có thể quan trọng.

Ngoài ra, một số khó khăn trong việc thực hiện DI thực hiện tốt hơn trong các dự án lớn. Nhìn chung, bạn sẽ di chuyển quyết định thực tế về việc sử dụng các lớp cụ thể nào trên hết, vì vậy phương thức bắt đầu của bạn có thể như sau:

public void Start()
{
    MySubSubInterfaceA mySubSubInterfaceA = new mySubSubInterfaceA();
    MySubSubInterfaceB mySubSubInterfaceB = new mySubSubInterfaceB();     
    MySubInterface mySubInterface = new MySubInterface(mySubSubInterfaceA,mySubSubInterfaceB);
    MyInterface myInterface = new MyInterface(MySubInterface);
    MyClass class = new MyClass(myInterface);
}

nhưng với 400 dòng mã loại khác. Nếu bạn tưởng tượng duy trì điều đó theo thời gian, sự hấp dẫn của các container DI trở nên rõ ràng hơn.

Ngoài ra, hãy tưởng tượng một lớp mà, giả sử, thực hiện IDis Dùng. Nếu các lớp sử dụng nó nhận được nó thông qua tiêm chứ không phải tự tạo ra nó, làm thế nào để họ biết khi nào gọi Dispose ()? (Đây là một vấn đề khác mà container DI giải quyết.)

Vì vậy, chắc chắn, tiêm phụ thuộc chỉ truyền một tham số, nhưng thực sự khi mọi người nói tiêm phụ thuộc có nghĩa là "truyền tham số cho đối tượng của bạn so với thay thế để có đối tượng của bạn khởi tạo một cái gì đó" và xử lý tất cả các lợi ích của nhược điểm một cách tiếp cận như vậy, có thể với sự trợ giúp của container DI.


Đó là rất nhiều phụ và giao diện! Bạn có nghĩa là mới lên một giao diện thay vì một lớp thực hiện giao diện? Có vẻ sai.
JBRWilkinson

@JBRWilkinson Ý tôi là một lớp thực hiện giao diện. Tôi nên làm cho rõ ràng hơn. Tuy nhiên, vấn đề là một ứng dụng lớn sẽ có các lớp với các phụ thuộc mà lần lượt có các phụ thuộc mà lần lượt có các phụ thuộc ... Thiết lập tất cả bằng tay trong phương thức Chính là kết quả của việc thực hiện nhiều thao tác xây dựng.
psr

4

Ở cấp độ, nó là dễ dàng.

'Dependency Injection' chỉ đơn giản trả lời câu hỏi "làm thế nào tôi tìm được cộng tác viên của mình" với "họ bị đẩy vào bạn - bạn không cần phải tự đi lấy chúng". (Nó tương tự như - nhưng không giống như - 'Đảo ngược điều khiển' trong đó câu hỏi "làm thế nào để tôi đặt hàng các hoạt động của mình trên đầu vào?" Có câu trả lời tương tự).

Các chỉ lợi ích mà có cộng tác viên của bạn đẩy vào bạn là cho phép mã khách hàng để sử dụng lớp học của bạn để soạn một đồ thị đối tượng đó phù hợp với nhu hiện tại của nó ... bạn có không được tự ý xác định trước hình dáng và mutability của đồ thị bằng cách tư nhân quyết định các loại cụ thể và vòng đời của các cộng tác viên của bạn.

(Tất cả những lợi ích khác, về khả năng kiểm tra, của khớp nối lỏng lẻo, v.v., phần lớn đến từ việc sử dụng các giao diện và không quá nhiều từ sự phụ thuộc-tiêm-tiêm, mặc dù DI tự nhiên thúc đẩy việc sử dụng các giao diện).

Điều đáng chú ý là, nếu bạn tránh khởi tạo các cộng tác viên của riêng mình, do đó , lớp của bạn phải lấy các cộng tác viên của nó từ một hàm tạo, một thuộc tính hoặc một đối số phương thức (tùy chọn sau này thường bị bỏ qua không phải lúc nào cũng có ý nghĩa đối với các cộng tác viên của một lớp là một phần của 'trạng thái' của nó).

Và đó là một điều tốt.

Ở cấp ứng dụng ...

Quá nhiều cho quan điểm trên mỗi lớp. Giả sử bạn có một nhóm các lớp tuân theo quy tắc "không khởi tạo các cộng tác viên của riêng bạn" và muốn tạo một ứng dụng từ họ. Điều đơn giản nhất để làm là sử dụng cũ tốt (một công cụ thực sự hữu ích để gọi các hàm tạo, thuộc tính và phương thức!) Để soạn đồ thị đối tượng bạn muốn và ném một số đầu vào vào nó. (Vâng, một số đối tượng trong biểu đồ của bạn sẽ là các nhà máy đối tượng, được chuyển qua làm cộng tác viên cho các đối tượng tồn tại lâu khác trong biểu đồ, sẵn sàng phục vụ ... bạn không thể xây dựng trước mọi đối tượng! ).

... bạn cần 'linh hoạt' định cấu hình biểu đồ đối tượng của ứng dụng ...

Tùy thuộc vào các mục tiêu khác (không liên quan đến mã) của bạn, bạn có thể muốn cung cấp cho người dùng cuối một số quyền kiểm soát đối với biểu đồ đối tượng do đó được triển khai. Điều này dẫn bạn theo hướng của sơ đồ cấu hình, loại này hay loại khác, cho dù đó là tệp văn bản của thiết kế của riêng bạn với một số cặp tên / giá trị, tệp XML, DSL tùy chỉnh, ngôn ngữ mô tả biểu đồ khai báo, chẳng hạn như YAML, một ngôn ngữ kịch bản bắt buộc như JavaScript hoặc một ngôn ngữ khác phù hợp với nhiệm vụ hiện tại. Bất cứ điều gì cần thiết để soạn một biểu đồ đối tượng hợp lệ, theo cách đáp ứng nhu cầu của người dùng của bạn.

... Có thể là một lực lượng thiết kế đáng kể.

Trong hoàn cảnh khắc nghiệt nhất của loại đó, bạn có thể chọn cách tiếp cận rất chung chung và cung cấp cho người dùng cuối một cơ chế chung để 'nối dây' biểu đồ đối tượng mà họ chọn và thậm chí cho phép họ cung cấp các giao diện cụ thể cho giao diện thời gian chạy! (Tài liệu của bạn là một viên ngọc sáng, người dùng của bạn rất thông minh, quen thuộc với ít nhất là phác thảo thô của biểu đồ đối tượng của ứng dụng của bạn, nhưng không có trình biên dịch tiện dụng). Kịch bản này về mặt lý thuyết có thể xảy ra trong một số tình huống 'doanh nghiệp'.

Trong trường hợp đó, bạn có thể có một ngôn ngữ khai báo cho phép người dùng của bạn thể hiện bất kỳ loại nào, thành phần của biểu đồ đối tượng của các loại đó và bảng giao diện mà người dùng cuối huyền thoại có thể trộn và khớp. Để giảm tải nhận thức cho người dùng của bạn, bạn thích cách tiếp cận 'cấu hình theo quy ước', để họ chỉ phải bước vào và vượt qua sự quan tâm của đồ thị đối tượng, thay vì vật lộn với toàn bộ.

Bạn nghèo quá!

Bởi vì bạn không thích tự viết ra tất cả những điều đó (nhưng nghiêm túc, hãy kiểm tra ràng buộc YAML cho ngôn ngữ của bạn), bạn đang sử dụng một khuôn khổ DI nào đó.

Tùy thuộc vào sự trưởng thành của khung đó, bạn có thể không có tùy chọn sử dụng hàm tạo, ngay cả khi nó có ý nghĩa (các cộng tác viên không thay đổi trong suốt vòng đời của một đối tượng), do đó buộc bạn phải sử dụng Setter Injection (ngay cả khi cộng tác viên không thay đổi trong suốt vòng đời của một đối tượng và ngay cả khi không thực sự có lý do hợp lý tại sao tất cả các triển khai cụ thể của giao diện phải có cộng tác viên của một loại cụ thể). Nếu vậy, bạn hiện đang ở trong địa ngục khớp nối mạnh mẽ, mặc dù đã siêng năng 'giao diện được sử dụng' trong toàn bộ cơ sở mã của bạn - kinh dị!

Mặc dù vậy, hy vọng rằng bạn đã sử dụng khung DI cung cấp cho bạn tùy chọn tiêm xây dựng và người dùng của bạn chỉ hơi khó chịu vì không dành nhiều thời gian suy nghĩ về những điều cụ thể họ cần để định cấu hình và cung cấp cho họ giao diện người dùng phù hợp hơn với nhiệm vụ trong tầm tay (Mặc dù công bằng mà nói, có lẽ bạn đã cố gắng nghĩ ra một cách, nhưng JavaEE đã làm bạn thất vọng và bạn phải dùng đến cách hack khủng khiếp này).

Bootnote

Bạn chưa bao giờ sử dụng Google Guice, điều này cung cấp cho bạn bộ mã hóa một cách để phân phối với nhiệm vụ soạn một biểu đồ đối tượng bằng mã ... bằng cách viết mã. Argh!


0

Rất nhiều người nói ở đây rằng DI là một cơ chế để khởi tạo nguồn khởi tạo của một biến thể hiện.

Đối với các ví dụ rõ ràng và đơn giản, bạn không thực sự cần "tiêm phụ thuộc". Bạn có thể làm điều này bằng một tham số đơn giản chuyển đến hàm tạo, như bạn đã lưu ý trong câu hỏi.

Tuy nhiên, tôi nghĩ rằng cơ chế "tiêm phụ thuộc" chuyên dụng rất hữu ích khi bạn nói về tài nguyên ở cấp ứng dụng, trong đó cả người gọi và callee đều không biết gì về các tài nguyên này (và không muốn biết!).

Ví dụ: Kết nối cơ sở dữ liệu, bối cảnh bảo mật, phương tiện ghi nhật ký, tệp cấu hình, v.v.

Hơn nữa, nói chung mỗi singleton là một ứng cử viên sáng giá cho DI. Bạn càng có nhiều và sử dụng chúng, bạn càng có nhiều cơ hội cần DI.

Đối với các loại tài nguyên này, rõ ràng là không có ý nghĩa gì khi di chuyển chúng xung quanh thông qua danh sách tham số và do đó DI được phát minh.

Lưu ý cuối cùng, bạn cũng cần một thùng chứa DI, sẽ thực hiện tiêm thực tế ... (một lần nữa, để giải phóng cả người gọi và callee khỏi chi tiết triển khai).


-2

Nếu bạn truyền vào một kiểu tham chiếu trừu tượng thì mã gọi có thể chuyển vào bất kỳ đối tượng nào mở rộng / thực hiện kiểu trừu tượng. Vì vậy, trong tương lai, lớp sử dụng đối tượng có thể sử dụng một đối tượng mới đã được tạo mà không cần sửa đổi mã của nó.


-4

Đây là một lựa chọn khác ngoài câu trả lời của amon

Sử dụng nhà xây dựng:

lớp Foo {
    Bar bar riêng;
    tư nhân Qux qux;

    riêng tư Foo () {
    }

    công khai Foo (Trình xây dựng)
        this .bar = builder.bar;
        this .qux = builder.qux;
    }

    Trình tạo lớp tĩnh công khai {
        Bar bar riêng;
        tư nhân Qux qux;
        công cộng Buider setBar (Bar bar) {
            this .bar = bar;
            trả lại cái này;
        }
        Trình tạo công cộng setQux (Qux qux) {
            this .qux = qux;
            trả lại cái này;
        }
        công khai Foo build () {
            trả lại Foo mới (cái này);
        }
    }
}

// trong sản xuất:
Foo foo = Foo mới.
    .setBar (Thanh mới ())
    .setQux (Qux mới ())
    .xây dựng();

// trong thử nghiệm:
Foo testFoo = Foo.Builder mới ()
    .setBar (BarMock mới ())
    .setQux (Qux mới ())
    .xây dựng();

Đây là những gì tôi sử dụng cho phụ thuộc. Tôi không sử dụng bất kỳ khung DI nào. Họ cản đường.


4
Điều này không thêm nhiều vào các câu trả lời khác từ một năm trước và không đưa ra lời giải thích nào về lý do tại sao một người xây dựng có thể giúp người hỏi hiểu về tiêm phụ thuộc.

@Snowman Đây là tùy chọn KHÁC thay cho DI. Tôi xin lỗi bạn không nhìn thấy nó theo cách đó. Cảm ơn đã bỏ phiếu.
dùng3155341

1
Người hỏi muốn hiểu nếu DI thực sự đơn giản như truyền một tham số, anh ta không yêu cầu thay thế cho DI.

1
Các ví dụ của OP rất tốt. Các ví dụ của bạn cung cấp sự phức tạp hơn cho một cái gì đó là một ý tưởng đơn giản. Họ không minh họa vấn đề của câu hỏi.
Adam Zuckerman

Con constructor DI đang truyền một tham số. Ngoại trừ bạn đang truyền một đối tượng giao diện chứ không phải là một đối tượng lớp cụ thể. Đây là 'sự phụ thuộc' vào việc triển khai cụ thể được giải quyết thông qua DI. Hàm tạo không biết loại đó đang được thông qua, nó cũng không quan tâm, nó chỉ biết rằng nó đang nhận một loại thực hiện giao diện. Tôi đề nghị PluralSight, Nó có một khóa học DI / IOC mới bắt đầu tuyệt vời.
Hardgraf
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.