Tại sao một người sử dụng tiêm phụ thuộc?


536

Tôi đang cố gắng để hiểu tiêm phụ thuộc (DI), và một lần nữa tôi đã thất bại. Nó chỉ có vẻ ngớ ngẩn. Mã của tôi không bao giờ là một mớ hỗn độn; Tôi hầu như không viết các hàm và giao diện ảo (mặc dù tôi đã thực hiện một lần trong một mặt trăng xanh) và tất cả cấu hình của tôi được nối tiếp một cách kỳ diệu vào một lớp bằng json.net (đôi khi sử dụng trình tuần tự XML).

Tôi không hiểu vấn đề gì nó giải quyết. Nó trông giống như một cách để nói: "xin chào. Khi bạn chạy vào hàm này, hãy trả về một đối tượng thuộc loại này và sử dụng các tham số / dữ liệu này."
Nhưng ... tại sao tôi lại sử dụng nó? Lưu ý tôi chưa bao giờ cần phải sử dụng object, nhưng tôi hiểu đó là để làm gì.

Một số tình huống thực tế trong việc xây dựng một trang web hoặc ứng dụng máy tính để bàn nơi người ta sẽ sử dụng DI là gì? Tôi có thể dễ dàng đưa ra các trường hợp về lý do tại sao một người nào đó có thể muốn sử dụng giao diện / chức năng ảo trong trò chơi, nhưng nó cực kỳ hiếm (đủ hiếm để tôi không thể nhớ một trường hợp duy nhất) để sử dụng mã đó trong mã không phải trò chơi.


3
Đây cũng có thể là thông tin hữu ích: martinfowler.com/articles/injection.html
ta.speot.is




5
Một cách giải thích rất đơn giản khác về DI: codearsenal.net/2015/03/ Mạnh
ybonda

Câu trả lời:


840

Đầu tiên, tôi muốn giải thích một giả định mà tôi đưa ra cho câu trả lời này. Điều này không phải lúc nào cũng đúng, nhưng khá thường xuyên:

Giao diện là tính từ; các lớp là danh từ.

(Trên thực tế, có những giao diện cũng là danh từ, nhưng tôi muốn khái quát ở đây.)

Vì vậy, ví dụ một giao diện có thể là một cái gì đó như IDisposable, IEnumerablehoặc IPrintable. Một lớp là một triển khai thực tế của một hoặc nhiều giao diện trong số này: Listhoặc Mapcả hai có thể là các triển khai của IEnumerable.

Để có được điểm: Thường thì các lớp của bạn phụ thuộc vào nhau. Ví dụ: bạn có thể có một Databaselớp truy cập cơ sở dữ liệu của bạn (hah, thật bất ngờ! ;-)), nhưng bạn cũng muốn lớp này đăng nhập về việc truy cập cơ sở dữ liệu. Giả sử bạn có một lớp khác Logger, sau đó Databasecó một phụ thuộc vào Logger.

Càng xa càng tốt.

Bạn có thể mô hình hóa sự phụ thuộc này trong Databaselớp của bạn với dòng sau:

var logger = new Logger();

và mọi thứ đều ổn Sẽ tốt cho đến ngày bạn nhận ra rằng bạn cần một loạt các logger: Đôi khi bạn muốn đăng nhập vào bảng điều khiển, đôi khi vào hệ thống tệp, đôi khi sử dụng TCP / IP và máy chủ ghi nhật ký từ xa, v.v.

Và tất nhiên bạn KHÔNG muốn thay đổi tất cả mã của mình (trong khi đó bạn có ánh mắt của nó) và thay thế tất cả các dòng

var logger = new Logger();

bởi:

var logger = new TcpLogger();

Đầu tiên, đây không phải là niềm vui. Thứ hai, đây là lỗi dễ xảy ra. Thứ ba, đây là công việc ngu ngốc, lặp đi lặp lại cho một con khỉ được đào tạo. Vậy bạn làm gì?

Rõ ràng đó là một ý tưởng khá tốt để giới thiệu một giao diện ICanLog(hoặc tương tự) được thực hiện bởi tất cả các logger khác nhau. Vì vậy, bước 1 trong mã của bạn là bạn làm:

ICanLog logger = new Logger();

Bây giờ kiểu suy luận không thay đổi kiểu nữa, bạn luôn có một giao diện duy nhất để phát triển. Bước tiếp theo là bạn không muốn new Logger()lặp đi lặp lại. Vì vậy, bạn đặt độ tin cậy để tạo các thể hiện mới cho một lớp nhà máy trung tâm duy nhất và bạn nhận được mã như:

ICanLog logger = LoggerFactory.Create();

Nhà máy tự quyết định loại logger để tạo. Mã của bạn không còn quan tâm nữa và nếu bạn muốn thay đổi loại logger đang được sử dụng, bạn hãy thay đổi nó một lần : Bên trong nhà máy.

Bây giờ, tất nhiên, bạn có thể khái quát hóa nhà máy này và làm cho nó hoạt động cho bất kỳ loại nào:

ICanLog logger = TypeFactory.Create<ICanLog>();

Ở đâu đó TypeFactory cần dữ liệu cấu hình mà lớp thực tế sẽ khởi tạo khi một loại giao diện cụ thể được yêu cầu, vì vậy bạn cần ánh xạ. Tất nhiên bạn có thể thực hiện ánh xạ này bên trong mã của mình, nhưng sau đó thay đổi kiểu có nghĩa là biên dịch lại. Nhưng bạn cũng có thể đặt ánh xạ này vào trong tệp XML, vd. Điều này cho phép bạn thay đổi lớp thực sự được sử dụng ngay cả sau khi biên dịch thời gian (!), Điều đó có nghĩa là động, không cần biên dịch lại!

Để cho bạn một ví dụ hữu ích cho việc này: Hãy nghĩ về một phần mềm không đăng nhập bình thường, nhưng khi khách hàng của bạn gọi và yêu cầu trợ giúp vì anh ta có vấn đề, tất cả những gì bạn gửi cho anh ta là một tệp cấu hình XML được cập nhật và giờ anh ta đã có đăng nhập được kích hoạt và hỗ trợ của bạn có thể sử dụng các tệp nhật ký để giúp khách hàng của bạn.

Và bây giờ, khi bạn thay thế tên một chút, bạn sẽ kết thúc bằng một triển khai đơn giản của Trình định vị dịch vụ , đây là một trong hai mẫu của Đảo ngược điều khiển (vì bạn đảo ngược điều khiển ai quyết định lớp chính xác nào sẽ khởi tạo).

Tất cả trong tất cả điều này làm giảm sự phụ thuộc trong mã của bạn, nhưng bây giờ tất cả mã của bạn có một phụ thuộc vào trình định vị dịch vụ trung tâm, duy nhất.

Việc tiêm phụ thuộc bây giờ là bước tiếp theo trong dòng này: Hãy loại bỏ sự phụ thuộc duy nhất này vào trình định vị dịch vụ: Thay vì các lớp khác nhau yêu cầu trình định vị dịch vụ triển khai cho một giao diện cụ thể, bạn - một lần nữa - hoàn nguyên quyền kiểm soát ai sẽ khởi tạo cái gì .

Với phép nội xạ phụ thuộc, Databaselớp của bạn hiện có một hàm tạo yêu cầu tham số kiểu ICanLog:

public Database(ICanLog logger) { ... }

Bây giờ cơ sở dữ liệu của bạn luôn có một trình ghi nhật ký để sử dụng, nhưng nó không biết thêm trình ghi nhật ký này đến từ đâu nữa.

Và đây là lúc khung DI phát huy tác dụng: Bạn định cấu hình ánh xạ của mình một lần nữa, sau đó yêu cầu khung DI của bạn khởi tạo ứng dụng cho bạn. Vì Applicationlớp yêu cầu ICanPersistDatatriển khai, một thể hiện của Databaseđược chèn - nhưng trước tiên, nó phải tạo một thể hiện của loại logger được cấu hình cho ICanLog. Và như thế ...

Vì vậy, để cắt ngắn một câu chuyện dài: Tiêm phụ thuộc là một trong hai cách để loại bỏ sự phụ thuộc trong mã của bạn. Nó rất hữu ích cho việc thay đổi cấu hình sau thời gian biên dịch, và đó là một điều tuyệt vời để thử nghiệm đơn vị (vì nó giúp dễ dàng tiêm cuống và / hoặc giả).

Trong thực tế, có những điều bạn không thể làm nếu không có bộ định vị dịch vụ (ví dụ: nếu bạn không biết trước có bao nhiêu trường hợp bạn cần một giao diện cụ thể: Khung DI luôn chỉ tiêm một phiên bản cho mỗi tham số, nhưng bạn có thể gọi một bộ định vị dịch vụ bên trong một vòng lặp, dĩ nhiên), do đó, hầu hết các khung DI cũng cung cấp một bộ định vị dịch vụ.

Nhưng về cơ bản, đó là nó.

PS: Những gì tôi mô tả ở đây là một kỹ thuật gọi là constructor tiêm , cũng có tiêm thuộc tính trong đó không phải là tham số của hàm tạo, nhưng các thuộc tính đang được sử dụng để xác định và giải quyết các phụ thuộc. Hãy nghĩ về tiêm tài sản như là một phụ thuộc tùy chọn, và tiêm xây dựng là phụ thuộc bắt buộc. Nhưng thảo luận về điều này là vượt quá phạm vi của câu hỏi này.


7
Tất nhiên bạn cũng có thể làm theo cách này, nhưng sau đó bạn phải triển khai logic này trong mỗi lớp duy nhất sẽ cung cấp hỗ trợ cho khả năng trao đổi thực hiện. Điều này có nghĩa là rất nhiều mã trùng lặp, dự phòng và điều này cũng có nghĩa là bạn cần chạm vào một lớp hiện có và viết lại một phần, một khi bạn quyết định rằng bạn cần nó ngay bây giờ. DI cho phép bạn sử dụng điều này trên bất kỳ lớp tùy ý nào, bạn không phải viết chúng theo một cách đặc biệt (ngoại trừ xác định các phụ thuộc là tham số trong hàm tạo).
Golo Roden

137
Đây là điều tôi không bao giờ nhận được khoảng DI: nó làm cho các kiến trúc bao la phức tạp hơn. Tuy nhiên, như tôi thấy, việc sử dụng khá hạn chế. Các ví dụ chắc chắn luôn giống nhau: logger có thể hoán đổi cho nhau, truy cập mô hình / dữ liệu có thể hoán đổi cho nhau. Đôi khi có thể hoán đổi cho nhau. Nhưng kia là nó. Những trường hợp này có thực sự biện minh cho một kiến ​​trúc phần mềm phức tạp hơn nhiều không? - Tiết lộ đầy đủ: Tôi đã sử dụng DI cho hiệu quả tuyệt vời, nhưng đó là một kiến ​​trúc trình cắm rất đặc biệt mà tôi sẽ không khái quát.
Konrad Rudolph

17
@GoloRoden, tại sao bạn gọi giao diện ICanLog thay vì ILogger? Tôi đã làm việc với một lập trình viên khác thường làm điều này, và tôi không bao giờ có thể hiểu được quy ước? Đối với tôi nó giống như gọi ICanEnum Cả IEn?
DermFbler

28
Tôi gọi nó là ICanLog, bởi vì chúng tôi làm việc quá thường xuyên với các từ (danh từ) không có nghĩa gì. Ví dụ, một nhà môi giới là gì? Một người quản lý? Ngay cả một Kho lưu trữ không được định nghĩa theo một cách duy nhất. Và có tất cả những điều này như danh từ là một căn bệnh điển hình của các ngôn ngữ OO (xem steve-yegge.blogspot.de/2006/03/ trên ). Điều tôi muốn bày tỏ là tôi có một thành phần có thể đăng nhập cho tôi - vậy tại sao không gọi nó theo cách đó? Tất nhiên, điều này cũng đang chơi với tôi với tư cách là người đầu tiên, do đó ICanLog (ForYou).
Golo Roden

18
@David Kiểm thử đơn vị hoạt động tốt - xét cho cùng, một đơn vị độc lập với những thứ khác (nếu không thì đó không phải là một đơn vị). Những gì không hoạt động mà không có container DI là thử nghiệm giả. Đủ công bằng, tôi không tin rằng lợi ích của việc chế nhạo vượt xa sự phức tạp thêm vào của việc thêm các thùng chứa DI trong mọi trường hợp. Tôi làm bài kiểm tra đơn vị nghiêm ngặt. Tôi hiếm khi làm chế giễu.
Konrad Rudolph

499

Tôi nghĩ rằng rất nhiều lần mọi người nhầm lẫn về sự khác biệt giữa tiêm phụ thuộckhung tiêm phụ thuộc (hoặc một thùng chứa như thường được gọi).

Phụ thuộc tiêm là một khái niệm rất đơn giản. Thay vì mã này:

public class A {
  private B b;

  public A() {
    this.b = new B(); // A *depends on* B
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  A a = new A();
  a.DoSomeStuff();
}

bạn viết mã như thế này:

public class A {
  private B b;

  public A(B b) { // A now takes its dependencies as arguments
    this.b = b; // look ma, no "new"!
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  B b = new B(); // B is constructed here instead
  A a = new A(b);
  a.DoSomeStuff();
}

Và đó là nó. Nghiêm túc. Điều này cung cấp cho bạn một tấn lợi thế. Hai yếu tố quan trọng là khả năng kiểm soát chức năng từ vị trí trung tâm ( Main()chức năng) thay vì trải rộng nó khắp chương trình của bạn và khả năng kiểm tra dễ dàng hơn từng lớp một cách riêng biệt (vì bạn có thể chuyển giả hoặc các đối tượng giả mạo khác vào hàm tạo của nó của một giá trị thực).

Hạn chế, tất nhiên, là bây giờ bạn có một hàm lớn biết về tất cả các lớp được sử dụng bởi chương trình của bạn. Đó là những gì khung DI có thể giúp với. Nhưng nếu bạn gặp khó khăn trong việc hiểu tại sao cách tiếp cận này có giá trị, trước tiên tôi khuyên bạn nên bắt đầu bằng cách tiêm phụ thuộc thủ công, vì vậy bạn có thể đánh giá cao hơn những khung công tác khác nhau có thể làm cho bạn.


7
Tại sao tôi thích mã thứ hai hơn là mã thứ nhất? từ đầu tiên chỉ có từ khóa mới, làm thế nào để giúp đỡ?
dùng962206

17
@ user962206 nghĩ về cách bạn sẽ kiểm tra A độc lập với B
jk.

66
@ user962206, cũng nghĩ về điều gì sẽ xảy ra nếu B cần một số tham số trong hàm tạo của nó: để khởi tạo nó, A sẽ phải biết về các tham số đó, một cái gì đó có thể hoàn toàn không liên quan đến A (nó chỉ muốn phụ thuộc vào B , không phụ thuộc vào những gì B phụ thuộc vào). Đi qua một B đã xây dựng (hoặc bất kỳ lớp con hoặc giả của B cho rằng vấn đề) để xây dựng giải quyết một nhân đó và làm cho A chỉ phụ thuộc vào B :)
epidemian

17
@ acidzombie24: Giống như nhiều mẫu thiết kế, DI không thực sự hữu ích trừ khi cơ sở mã của bạn đủ lớn để cách tiếp cận đơn giản trở thành vấn đề. Cảm giác thật lòng của tôi là DI sẽ không thực sự là một cải tiến cho đến khi ứng dụng của bạn có hơn 20.000 dòng mã và / hoặc hơn 20 phụ thuộc vào các thư viện hoặc khung khác. Nếu ứng dụng của bạn nhỏ hơn thế, bạn vẫn có thể thích lập trình theo kiểu DI, nhưng sự khác biệt sẽ không đáng kể.
Daniel Pryden

2
@DanielPryden Tôi không nghĩ kích thước mã quan trọng bằng mã động của bạn như thế nào. nếu bạn thường xuyên thêm các mô-đun mới phù hợp với cùng một giao diện, bạn sẽ không phải thay đổi mã phụ thuộc thường xuyên.
FistOfFury

35

Như các câu trả lời khác đã nêu, tiêm phụ thuộc là một cách để tạo ra các phụ thuộc của bạn bên ngoài lớp sử dụng nó. Bạn tiêm chúng từ bên ngoài, và kiểm soát sự sáng tạo của chúng từ bên trong lớp học của bạn. Đây cũng là lý do tại sao tiêm phụ thuộc là một hiện thực của nguyên tắc Inversion of control (IoC).

IoC là nguyên tắc, trong đó DI là mô hình. Lý do mà bạn có thể "cần nhiều hơn một logger" không bao giờ thực sự được đáp ứng, theo như kinh nghiệm của tôi, nhưng lý do thực tế là, bạn thực sự cần nó, bất cứ khi nào bạn kiểm tra một cái gì đó. Một ví dụ:

Tính năng của tôi:

Khi tôi xem một đề nghị, tôi muốn đánh dấu rằng tôi đã xem nó một cách tự động, để tôi không quên làm điều đó.

Bạn có thể kiểm tra như thế này:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var formdata = { . . . }

    // System under Test
    var weasel = new OfferWeasel();

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}

Vì vậy, ở đâu đó OfferWeasel, nó xây dựng cho bạn một đối tượng cung cấp như thế này:

public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}

Vấn đề ở đây là, bài kiểm tra này rất có thể sẽ luôn thất bại, vì ngày được đặt sẽ khác với ngày được xác nhận, ngay cả khi bạn chỉ nhập DateTime.Nowmã kiểm tra, nó có thể bị tắt trong vài mili giây và do đó sẽ luôn luôn thất bại Một giải pháp tốt hơn bây giờ sẽ là tạo một giao diện cho việc này, cho phép bạn kiểm soát thời gian sẽ được đặt:

public interface IGotTheTime
{
    DateTime Now {get;}
}

public class CannedTime : IGotTheTime
{
    public DateTime Now {get; set;}
}

public class ActualTime : IGotTheTime
{
    public DateTime Now {get { return DateTime.Now; }}
}

public class OfferWeasel
{
    private readonly IGotTheTime _time;

    public OfferWeasel(IGotTheTime time)
    {
        _time = time;
    }

    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = _time.Now;
        return offer;
    }
}

Giao diện là sự trừu tượng. Một là điều THỰC SỰ, và một điều khác cho phép bạn giả mạo một lúc nào đó khi cần thiết. Bài kiểm tra sau đó có thể được thay đổi như thế này:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
    var formdata = { . . . }

    var time = new CannedTime { Now = date };

    // System under test
    var weasel= new OfferWeasel(time);

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(date);
}

Như thế này, bạn đã áp dụng nguyên tắc "đảo ngược kiểm soát", bằng cách tiêm một phụ thuộc (lấy thời gian hiện tại). Lý do chính để làm điều này là để thử nghiệm đơn vị dễ bị cô lập hơn, có nhiều cách khác để làm điều đó. Ví dụ, một giao diện và một lớp ở đây là không cần thiết vì trong các hàm C # có thể được truyền xung quanh dưới dạng các biến, vì vậy thay vì một giao diện bạn có thể sử dụng một giao diện Func<DateTime>để đạt được điều tương tự. Hoặc, nếu bạn thực hiện một cách tiếp cận năng động, bạn chỉ cần vượt qua bất kỳ đối tượng nào có phương thức tương đương ( gõ vịt ) và bạn không cần một giao diện nào cả.

Bạn sẽ hầu như không cần nhiều hơn một logger. Tuy nhiên, tiêm phụ thuộc là điều cần thiết cho mã được nhập tĩnh như Java hoặc C #.

Và ... Cũng cần lưu ý rằng một đối tượng chỉ có thể thực hiện đúng mục đích của nó khi chạy, nếu tất cả các phụ thuộc của nó có sẵn, do đó không có nhiều sử dụng trong việc thiết lập tiêm thuộc tính. Theo tôi, tất cả các phụ thuộc nên được thỏa mãn khi hàm tạo đang được gọi, do đó, hàm tạo của hàm dựng là điều cần làm.

Tôi hy vọng điều đó đã giúp.


4
Đó thực sự trông giống như một giải pháp khủng khiếp. Tôi chắc chắn sẽ viết mã giống như câu trả lời của Daniel Pryden nhưng đối với bài kiểm tra đơn vị cụ thể đó, tôi chỉ cần thực hiện DateTime. Làm thế nào trước và sau chức năng và kiểm tra xem thời gian có ở giữa không? Thêm nhiều giao diện / nhiều dòng mã hơn có vẻ như là một ý tưởng tồi đối với tôi.

3
Tôi không thích các ví dụ A (B) chung chung và tôi chưa bao giờ cảm thấy một logger được yêu cầu phải có 100 triển khai. Đây là một ví dụ mà tôi mới gặp và đây là một trong 5 cách để giải quyết nó, trong đó một cách thực sự bao gồm sử dụng PostSharp. Nó minh họa một cách tiếp cận tiêm ctor cổ điển dựa trên lớp. Bạn có thể cung cấp một ví dụ thực tế tốt hơn về nơi bạn gặp phải việc sử dụng tốt cho DI không?
giám khảo

2
Tôi chưa bao giờ thấy một sử dụng tốt cho DI. Đó là lý do tại sao tôi viết câu hỏi.

2
Tôi không thấy nó hữu ích. Mã của tôi luôn luôn dễ dàng để làm bài kiểm tra. Dường như DI tốt cho các cơ sở mã lớn với mã xấu.

1
Xin lưu ý rằng ngay cả trong các chương trình chức năng nhỏ, bất cứ khi nào bạn có f (x), g (f) bạn đã sử dụng phép tiêm phụ thuộc, do đó, mỗi lần tiếp tục trong JS sẽ được tính là một lần tiêm phụ thuộc. Tôi đoán là bạn đã sử dụng nó;)
giám sát viên

15

Tôi nghĩ rằng câu trả lời kinh điển là tạo ra một ứng dụng tách rời hơn, không có kiến ​​thức về việc triển khai nào sẽ được sử dụng trong thời gian chạy.

Ví dụ: chúng tôi là nhà cung cấp thanh toán trung tâm, làm việc với nhiều nhà cung cấp thanh toán trên toàn thế giới. Tuy nhiên, khi yêu cầu được đưa ra, tôi không biết mình sẽ gọi bộ xử lý thanh toán nào. Tôi có thể lập trình một lớp với rất nhiều trường hợp chuyển đổi, chẳng hạn như:

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

Bây giờ hãy tưởng tượng rằng bây giờ bạn sẽ cần duy trì tất cả mã này trong một lớp vì nó không được tách riêng đúng cách, bạn có thể tưởng tượng rằng với mỗi bộ xử lý mới bạn sẽ hỗ trợ, bạn sẽ cần tạo một trường hợp mới nếu // chuyển đổi cho Tuy nhiên, mọi phương pháp, điều này chỉ trở nên phức tạp hơn, tuy nhiên, bằng cách sử dụng Dependency Injection (hoặc Inversion of Control - như đôi khi nó được gọi, có nghĩa là bất cứ ai kiểm soát việc chạy chương trình chỉ được biết đến trong thời gian chạy, và không phức tạp), bạn có thể đạt được điều gì đó rất gọn gàng và bảo trì.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

** Mã sẽ không được biên dịch, tôi biết :)


+1 vì có vẻ như bạn đang nói rằng bạn cần nó ở nơi bạn sử dụng các phương thức / giao diện ảo. Nhưng đó vẫn là hiếm. Tôi vẫn sẽ chuyển nó vào new ThatProcessor()thay vì sử dụng một khung công tác

@ItaiS Bạn có thể tránh vô số công tắc với mẫu thiết kế nhà máy lớp. Sử dụng hệ thống phản chiếu.Reflection.Assugging.GetExecutingAssugging (). CreateInstance ()
domenicr

@domenicr dĩ nhiên! nhưng tôi muốn giải thích nó trong một ví dụ đơn giản
Itai Sagi

Tôi đồng ý với lời giải thích trên, ngoại trừ sự cần thiết của lớp nhà máy. Thời điểm chúng tôi thực hiện lớp nhà máy của nó chỉ đơn giản là một lựa chọn thô. Lời giải thích tốt nhất ở trên mà tôi tìm thấy trong Chương Poymorphism và chức năng ảo của Bruce Erkel. DI thực sự phải được tự do khỏi lựa chọn và loại đối tượng sẽ được quyết định tại thời điểm chạy thông qua giao diện. Đó cũng là những gì hành vi đa hình thực sự là.
Arvind Krmar

Ví dụ (theo c ++), chúng ta có một giao diện chung chỉ lấy tham chiếu đến lớp cơ sở và thực hiện hành vi của lớp dẫn xuất của nó mà không cần chọn. void Tune (Nhạc cụ & i) {i.play (middleC); } int main () {Sáo gió; giai điệu (sáo); } Nhạc cụ là lớp cơ sở, gió có nguồn gốc từ nó. Theo c ++, hàm ảo làm cho điều này có thể thực hiện hành vi của lớp dẫn xuất thông qua giao diện chung.
Arvind Krmar

6

Lý do chính để sử dụng DI là bạn muốn đặt trách nhiệm về kiến ​​thức thực hiện nơi kiến ​​thức ở đó. Ý tưởng của DI rất nhiều nội tuyến với đóng gói và thiết kế theo giao diện. Nếu mặt trước yêu cầu từ mặt sau cho một số dữ liệu, thì nó không quan trọng đối với mặt trước làm thế nào mặt sau giải quyết câu hỏi đó. Đó là tùy thuộc vào requesthandler.

Điều đó đã phổ biến trong OOP trong một thời gian dài. Nhiều lần tạo các đoạn mã như:

I_Dosomething x = new Impl_Dosomething();

Hạn chế là lớp triển khai vẫn được mã hóa cứng, do đó có mặt trước kiến ​​thức mà việc triển khai được sử dụng. DI đưa thiết kế bằng giao diện tiến thêm một bước, điều duy nhất mà giao diện người dùng cần biết là kiến ​​thức về giao diện. Ở giữa DYI và DI là mẫu của trình định vị dịch vụ, bởi vì giao diện người dùng phải cung cấp khóa (có trong sổ đăng ký của trình định vị dịch vụ) để cho phép yêu cầu của nó được giải quyết. Ví dụ về định vị dịch vụ:

I_Dosomething x = ServiceLocator.returnDoing(String pKey);

Ví dụ DI:

I_Dosomething x = DIContainer.returnThat();

Một trong những yêu cầu của DI là container phải có khả năng tìm ra lớp nào là sự thực thi của giao diện nào. Do đó, một container DI yêu cầu thiết kế gõ mạnh và chỉ có một triển khai cho mỗi giao diện cùng một lúc. Nếu bạn cần triển khai nhiều giao diện hơn cùng một lúc (như máy tính), bạn cần bộ định vị dịch vụ hoặc mẫu thiết kế nhà máy.

D (b) I: Phụ thuộc tiêm và thiết kế theo giao diện. Hạn chế này không phải là một vấn đề thực tế rất lớn mặc dù. Lợi ích của việc sử dụng D (b) I là nó phục vụ giao tiếp giữa khách hàng và nhà cung cấp. Giao diện là một phối cảnh trên một đối tượng hoặc một tập hợp các hành vi. Thứ hai là rất quan trọng ở đây.

Tôi thích quản trị các hợp đồng dịch vụ cùng với D (b) I trong mã hóa. Họ nên đi cùng nhau. Việc sử dụng D (b) I như một giải pháp kỹ thuật mà không có quản trị tổ chức hợp đồng dịch vụ không có lợi cho quan điểm của tôi, bởi vì DI sau đó chỉ là một lớp đóng gói bổ sung. Nhưng khi bạn có thể sử dụng nó cùng với quản trị tổ chức, bạn thực sự có thể sử dụng nguyên tắc tổ chức D (b) tôi cung cấp. Nó có thể giúp bạn về lâu dài để cấu trúc giao tiếp với khách hàng và các bộ phận kỹ thuật khác trong các chủ đề như thử nghiệm, phiên bản và phát triển các lựa chọn thay thế. Khi bạn có một giao diện ngầm như trong một lớp được mã hóa cứng, thì nó sẽ ít giao tiếp hơn theo thời gian sau đó khi bạn làm cho nó rõ ràng bằng cách sử dụng D (b) I. Tất cả sôi sục để bảo trì, đó là theo thời gian và không tại một thời điểm. :-)


1
"Hạn chế là lớp triển khai vẫn bị mã hóa cứng" <- hầu hết thời gian chỉ có một triển khai và như tôi đã nói tôi không thể nghĩ ra mã nongame yêu cầu giao diện chưa được xây dựng (.NET ).

@ acidzombie24 Có thể ... nhưng so sánh nỗ lực thực hiện giải pháp sử dụng DI ngay từ đầu với nỗ lực thay đổi giải pháp không phải DI sau này nếu bạn cần giao diện. Tôi gần như luôn luôn đi với tùy chọn đầu tiên. Bây giờ tốt hơn là trả 100 đô la thay vì phải trả 100.000 đô la vào ngày mai.
Golo Roden

1
@GoloRoden Thật vậy, bảo trì là vấn đề chính khi sử dụng các kỹ thuật như D (b) I. Đó là 80% chi phí của một ứng dụng. Một thiết kế trong đó hành vi cần thiết được thực hiện rõ ràng bằng cách sử dụng các giao diện từ đầu giúp tiết kiệm cho tổ chức sau này rất nhiều thời gian và tiền bạc.
Loek Bergman

Tôi sẽ không hiểu thực sự cho đến khi tôi phải trả nó vì cho đến nay tôi đã trả $ 0 và cho đến nay tôi vẫn chỉ cần trả $ 0. Nhưng tôi phải trả 0,05 đô la để giữ cho mọi dòng hoặc chức năng sạch sẽ.
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.