Dlaczego używa się wstrzykiwania zależności?

536

Próbuję zrozumieć zastrzyki zależności (DI) i po raz kolejny zawiodłem. To po prostu wydaje się głupie. Mój kod nigdy nie jest bałaganem; Prawie nie piszę funkcji wirtualnych i interfejsów (chociaż robię to raz na niebieskim księżycu), a cała moja konfiguracja jest magicznie serializowana do klasy za pomocą json.net (czasem za pomocą serializatora XML).

Nie do końca rozumiem, jaki problem rozwiązuje. Wygląda to na sposób powiedzenia: „cześć. Po uruchomieniu tej funkcji zwróć obiekt tego typu i wykorzystujący te parametry / dane”.
Ale ... dlaczego miałbym tego używać? Uwaga: Nigdy też nie musiałem używać object, ale rozumiem, do czego to służy.

Jakie są rzeczywiste sytuacje w budowaniu strony internetowej lub aplikacji komputerowej, w których można użyć DI? Mogę łatwo wymyślić przypadki, dla których ktoś może chcieć używać interfejsów / funkcji wirtualnych w grze, ale jest to niezwykle rzadkie (na tyle rzadkie, że nie pamiętam ani jednej instancji) użycie tego w kodzie innym niż gra.

TJ Crowder
źródło
3
Może to być także przydatna informacja: martinfowler.com/articles/injection.html
ta.speot.is 13.01.2013
1
Zobacz także .
BlueRaja - Danny Pflughoeft
41
Jestem z tym facetem: jamesshore.com/Blog/Dependency-Injection-Demystified.html .
Eric Lippert,
5
Kolejne bardzo proste wyjaśnienie DI: codearsenal.net/2015/03/…
ybonda

Odpowiedzi:

840

Po pierwsze, chcę wyjaśnić założenie, że przyjmę tę odpowiedź. Nie zawsze jest to prawda, ale dość często:

Interfejsy są przymiotnikami; klasy są rzeczownikami.

(W rzeczywistości istnieją również interfejsy, które są rzeczownikami, ale chcę tutaj uogólnić.)

Tak więc np. Interfejs może być czymś takim jak IDisposable, IEnumerablelub IPrintable. Klasa jest faktyczną implementacją jednego lub więcej z tych interfejsów: Listlub Mapobie mogą być implementacjami IEnumerable.

Aby uzyskać punkt: często twoje zajęcia zależą od siebie. Np. Możesz mieć Databaseklasę, która uzyskuje dostęp do twojej bazy danych (hah, niespodzianka! ;-)), ale chcesz też, aby ta klasa rejestrowała dostęp do bazy danych. Załóżmy, że masz inną klasę Logger, a następnie Databasezależność od niej Logger.

Na razie w porządku.

Możesz modelować tę zależność w swojej Databaseklasie za pomocą następującego wiersza:

var logger = new Logger();

i wszystko jest w porządku. Do dnia, w którym zdasz sobie sprawę, że potrzebujesz kilku rejestratorów, jest w porządku: Czasami chcesz zalogować się do konsoli, czasem do systemu plików, czasem używając TCP / IP i zdalnego serwera rejestrowania i tak dalej ...

I oczywiście NIE chcesz zmieniać całego kodu (tymczasem masz jego gazilliony) i zastępować wszystkie wiersze

var logger = new Logger();

przez:

var logger = new TcpLogger();

Po pierwsze, to nie jest zabawa. Po drugie, jest to podatne na błędy. Po trzecie, jest to głupia, powtarzalna praca dla wyszkolonej małpy. Więc co robisz?

Oczywiście całkiem dobrym pomysłem jest wprowadzenie interfejsu ICanLog(lub podobnego), który jest implementowany przez wszystkie różne programy rejestrujące. Więc krok 1 w twoim kodzie to:

ICanLog logger = new Logger();

Teraz wnioskowanie o typach nie zmienia już typu, zawsze masz jeden interfejs do opracowania. Następnym krokiem jest to, że nie chcesz mieć new Logger()w kółko. Dlatego stawiasz niezawodność, aby tworzyć nowe wystąpienia w jednej, centralnej klasie fabryki, i otrzymujesz kod, taki jak:

ICanLog logger = LoggerFactory.Create();

Sama fabryka decyduje, jaki rodzaj rejestratora utworzyć. Twój kod już się nie przejmuje, a jeśli chcesz zmienić typ używanego rejestratora, zmienisz go raz : wewnątrz fabryki.

Teraz oczywiście możesz uogólnić tę fabrykę i sprawić, by działała dla dowolnego typu:

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

Gdzieś ten TypeFactory potrzebuje danych konfiguracyjnych, które rzeczywista klasa utworzy, gdy zostanie zażądany określony typ interfejsu, więc potrzebujesz mapowania. Oczywiście możesz wykonać to mapowanie w kodzie, ale zmiana typu oznacza rekompilację. Ale możesz również umieścić to mapowanie w pliku XML, np. Pozwala to zmienić faktycznie używaną klasę nawet po czasie kompilacji (!), Co oznacza dynamicznie, bez ponownej kompilacji!

Aby dać ci użyteczny przykład: Pomyśl o oprogramowaniu, które nie loguje się normalnie, ale kiedy klient dzwoni i prosi o pomoc, ponieważ ma problem, wszystko, co mu wysyłasz, to zaktualizowany plik konfiguracyjny XML, a teraz ma rejestrowanie włączone, a obsługa klienta może użyć plików dziennika, aby pomóc klientowi.

A teraz, kiedy trochę zastąpisz nazwy, kończysz się prostą implementacją Lokalizatora usług , który jest jednym z dwóch wzorców Inwersji Kontroli (ponieważ odwracasz kontrolę nad tym, kto decyduje, którą konkretną klasę utworzyć).

Podsumowując, zmniejsza to zależności w kodzie, ale teraz cały kod jest zależny od centralnego lokalizatora pojedynczej usługi.

Wstrzykiwanie zależności jest teraz kolejnym krokiem w tej linii: pozbądź się tej pojedynczej zależności od lokalizatora usług: zamiast różnych klas pytających lokalizator usług o implementację dla określonego interfejsu, ty - po raz kolejny - przywracasz kontrolę nad tym, kto tworzy co .

Dzięki wstrzykiwaniu zależności Databaseklasa ma teraz konstruktor, który wymaga parametru typu ICanLog:

public Database(ICanLog logger) { ... }

Teraz twoja baza danych zawsze ma logger do użycia, ale nie wie już, skąd ten logger pochodzi.

I tu właśnie pojawia się środowisko DI: Ponownie konfigurujesz swoje odwzorowania, a następnie pytasz środowisko DI o utworzenie dla Ciebie aplikacji. Ponieważ Applicationklasa wymaga ICanPersistDataimplementacji, instancja Databasejest wstrzykiwana - ale w tym celu musi najpierw utworzyć instancję tego rodzaju programu rejestrującego, dla którego jest skonfigurowana ICanLog. I tak dalej ...

Krótko mówiąc: wstrzykiwanie zależności jest jednym z dwóch sposobów usuwania zależności w kodzie. Jest to bardzo przydatne do zmian konfiguracji po czasie kompilacji i jest świetną rzeczą do testowania jednostkowego (ponieważ bardzo łatwo wstrzykuje kody pośredniczące i / lub próbne).

W praktyce są rzeczy, których nie można obejść bez lokalizatora usług (np. Jeśli nie wiesz z góry, ile instancji potrzebujesz konkretnego interfejsu: Framework DI zawsze wstrzykuje tylko jedną instancję na parametr, ale możesz wywołać lokalizator usług wewnątrz pętli, oczywiście), dlatego najczęściej każda struktura DI zapewnia również lokalizator usług.

Ale w zasadzie to tyle.

PS: To, co tu opisałem, to technika nazywana wstrzykiwaniem konstruktora , jest też wstrzykiwanie właściwości, gdzie nie są parametry konstruktora, ale właściwości są używane do definiowania i rozwiązywania zależności. Pomyśl o wstrzykiwaniu właściwości jako opcjonalnej zależności, a o wstrzykiwaniu konstruktora o obowiązkowych zależności. Ale dyskusja na ten temat wykracza poza zakres tego pytania.

Golo Roden
źródło
7
Oczywiście możesz to zrobić również w ten sposób, ale musisz zaimplementować tę logikę w każdej klasie, która zapewni obsługę wymienności implementacji. Oznacza to wiele zduplikowanych, zbędnych kodów, a to oznacza także, że musisz dotknąć istniejącej klasy i przepisać ją częściowo, gdy zdecydujesz, że potrzebujesz go teraz. DI pozwala na użycie tego na dowolnej dowolnej klasie, nie musisz zapisywać ich w specjalny sposób (z wyjątkiem definiowania zależności jako parametrów w konstruktorze).
Golo Roden
137
Oto, czego nigdy nie rozumiem w DI: sprawia, że ​​architektura jest znacznie bardziej skomplikowana. A jednak, jak widzę, użycie jest dość ograniczone. Przykłady są z pewnością zawsze takie same: wymienne rejestratory, wymienny dostęp do modelu / danych. Czasami wymienny widok. Ale to jest to. Czy te kilka przypadków naprawdę uzasadnia znacznie bardziej złożoną architekturę oprogramowania? - Pełne ujawnienie: Użyłem już DI z doskonałym skutkiem, ale dotyczyło to bardzo specjalnej architektury wtyczek, z której nie chciałbym generalizować.
Konrad Rudolph
17
@GoloRoden, dlaczego wywołujesz interfejs ICanLog zamiast ILogger? Pracowałem z innym programistą, który często to robił i nigdy nie mogłem zrozumieć konwencji? Dla mnie jest to jak wywoływanie IEnumerable ICanEnumerate?
DermFrench
28
Nazwałem to ICanLog, ponieważ zbyt często pracujemy ze słowami (rzeczownikami), które nic nie znaczą. Np. Czym jest Broker? Menedżer? Nawet repozytorium nie jest zdefiniowane w unikalny sposób. A posiadanie tych wszystkich rzeczy jako rzeczowników jest typową chorobą języków OO (patrz steve-yegge.blogspot.de/2006/03/… ). Chcę wyrazić, że mam komponent, który może dla mnie rejestrować - dlaczego więc nie nazwać tego w ten sposób? Oczywiście, to także gra z I jako pierwszą osobą, stąd ICanLog (ForYou).
Golo Roden,
18
@David Testowanie jednostek działa dobrze - w końcu jednostka jest niezależna od innych rzeczy (w przeciwnym razie nie jest to jednostka). To, co nie działa bez kontenerów DI, jest próbnym testowaniem. W porządku, nie jestem przekonany, że korzyść z kpin przewyższa dodatkową złożoność dodawania pojemników DI we wszystkich przypadkach. Przeprowadzam rygorystyczne testy jednostkowe. Rzadko kpię.
Konrad Rudolph
499

Myślę, że wiele razy ludzie się mylić o różnicę między wstrzykiwania zależności i iniekcji zależność ram (lub pojemnika jak to często nazywa).

Zastrzyk zależności jest bardzo prostą koncepcją. Zamiast tego kodu:

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

piszesz taki kod:

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

I to wszystko. Poważnie. Daje to wiele korzyści. Dwie ważne z nich to zdolność do kontrolowania funkcjonalności z centralnego miejsca ( Main()funkcji) zamiast rozprowadzania jej w całym programie oraz zdolność do łatwiejszego testowania każdej klasy w izolacji (ponieważ zamiast tego można przekazywać symulatory lub inne fałszywe obiekty do konstruktora o rzeczywistej wartości).

Wadą jest oczywiście to, że masz teraz jedną mega-funkcję, która wie o wszystkich klasach używanych przez twój program. W tym mogą pomóc frameworki DI. Ale jeśli masz problemy ze zrozumieniem, dlaczego to podejście jest tak cenne, zalecam najpierw ręczne wstrzyknięcie zależności, abyś mógł lepiej docenić, co mogą dla ciebie zrobić różne frameworki.

Daniel Pryden
źródło
7
Dlaczego wolałbym drugi kod zamiast pierwszego? pierwszy ma tylko nowe słowo kluczowe, jak to pomaga?
user962206
17
@ user962206 pomyśl o tym, jak przetestowałbyś A niezależnie od B
jk.
66
@ user962206 również zastanów się, co by się stało, gdyby B potrzebował pewnych parametrów w swoim konstruktorze: aby utworzyć instancję, A musiałby wiedzieć o tych parametrach, coś, co może być całkowicie niezwiązane z A (po prostu chce zależeć od B , nie od tego, od czego zależy B). Przekazywanie już skonstruowanego B (lub jakiejkolwiek podklasy lub makiety B w tym przypadku) konstruktorowi A rozwiązuje to i sprawia, że ​​A zależy tylko od B :)
epidemia
17
@ acidzombie24: Podobnie jak wiele wzorców projektowych, DI nie jest tak naprawdę użyteczny, chyba że baza kodu jest wystarczająco duża, aby proste podejście stało się problemem. Mam przeczucie, że DI nie będzie tak naprawdę poprawą, dopóki twoja aplikacja nie będzie zawierała więcej niż około 20 000 linii kodu i / lub więcej niż 20 zależności od innych bibliotek lub frameworków. Jeśli twoja aplikacja jest mniejsza, nadal możesz chcieć programować w stylu DI, ale różnica nie będzie tak dramatyczna.
Daniel Pryden
2
@DanielPryden Nie sądzę, aby rozmiar kodu był tak ważny, jak dynamiczny kod. jeśli regularnie dodajesz nowe moduły, które pasują do tego samego interfejsu, nie będziesz musiał tak często zmieniać kodu zależnego.
FistOfFury
35

Jak stwierdzono w innych odpowiedziach, wstrzykiwanie zależności jest sposobem na tworzenie zależności poza klasą, która z nich korzysta. Wstrzykujesz je z zewnątrz i przejmujesz kontrolę nad ich stworzeniem z wnętrza klasy. Dlatego też wstrzykiwanie zależności jest realizacją zasady Inversion of control (IoC).

IoC jest zasadą, w której DI jest wzorem. Z mojego doświadczenia wynika, że ​​możesz potrzebować więcej niż jednego rejestratora, ale tak naprawdę to dlatego, że naprawdę go potrzebujesz, za każdym razem, gdy coś testujesz. Przykład:

Moja funkcja:

Kiedy patrzę na ofertę, chcę zaznaczyć, że spojrzałem na nią automatycznie, aby nie zapomnieć o tym.

Możesz to przetestować w następujący sposób:

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

Gdzieś w OfferWeaselbuduje ci ofertę Obiekt taki jak ten:

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

Problem polega na tym, że ten test najprawdopodobniej zawsze się nie powiedzie, ponieważ ustawiana data będzie się różnić od daty, o której mowa, nawet jeśli po prostu wstawisz DateTime.Nowkod testowy, może on być wyłączony o kilka milisekund, a zatem zawsze zawodzi. Lepszym rozwiązaniem byłoby teraz utworzenie do tego interfejsu, który pozwala kontrolować, która godzina zostanie ustawiona:

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

Interfejs jest abstrakcją. Jedna jest PRAWDZIWĄ rzeczą, a druga pozwala udawać, że jest potrzebna. Test można następnie zmienić w następujący sposób:

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

W ten sposób zastosowałeś zasadę „inwersji kontroli”, wstrzykując zależność (uzyskanie bieżącego czasu). Głównym powodem tego jest łatwiejsze izolowane testowanie jednostek, istnieją inne sposoby na to. Na przykład interfejs i klasa tutaj nie są potrzebne, ponieważ w języku C # funkcje mogą być przekazywane jako zmienne, więc zamiast interfejsu można użyć a, Func<DateTime>aby osiągnąć to samo. Lub, jeśli zastosujesz podejście dynamiczne, po prostu przekazujesz dowolny obiekt, który ma równoważną metodę ( pisanie kaczką ), i wcale nie potrzebujesz interfejsu.

Prawie nigdy nie będziesz potrzebować więcej niż jednego rejestratora. Niemniej jednak wstrzykiwanie zależności jest niezbędne w przypadku kodu o typie statycznym, takiego jak Java lub C #.

I ... Należy również zauważyć, że obiekt może poprawnie wypełniać swój cel tylko w środowisku wykonawczym, o ile wszystkie jego zależności są dostępne, więc konfigurowanie wstrzykiwania właściwości jest mało przydatne. Moim zdaniem, wszystkie zależności powinny być spełnione, gdy wywoływany jest konstruktor, więc do tego należy się stosować.

Mam nadzieję, że to pomogło.

procesor
źródło
4
To faktycznie wygląda na okropne rozwiązanie. Zdecydowanie napisałbym kod bardziej jak sugeruje odpowiedź Daniela Prydena , ale dla tego konkretnego testu jednostkowego po prostu wykonałem DateTime. Teraz przed i po funkcji i sprawdziłem, czy czas jest pomiędzy? Dodanie kolejnych interfejsów / znacznie więcej linii kodu wydaje mi się złym pomysłem.
3
Nie lubię ogólnych przykładów A (B) i nigdy nie czułem, że rejestrator wymaga 100 implementacji. Jest to przykład, z którym ostatnio się spotkałem i jest to jeden z 5 sposobów jego rozwiązania, w tym jeden z nich obejmuje użycie PostSharp. Ilustruje klasyczne podejście oparte na wstrzykiwaniu ctor. Czy możesz podać lepszy przykład rzeczywistego wykorzystania DI w świecie rzeczywistym?
procesor
2
Nigdy nie widziałem dobrego zastosowania DI. Właśnie dlatego napisałem pytanie.
2
Nie uważam tego za pomocne. Mój kod jest zawsze łatwy do przetestowania. Wydaje się, że DI jest dobre dla dużych baz kodu ze złym kodem.
1
Należy pamiętać, że nawet w małych programach funkcjonalnych, ilekroć masz f (x), g (f), już używasz wstrzykiwania zależności, więc każda kontynuacja w JS będzie liczyła się jako zastrzyk zależności. Domyślam się, że już go używasz;)
procesor
15

Myślę, że klasyczną odpowiedzią jest stworzenie bardziej oddzielonej aplikacji, która nie ma wiedzy o tym, która implementacja zostanie użyta w czasie wykonywania.

Na przykład jesteśmy centralnym dostawcą płatności, współpracującym z wieloma dostawcami płatności na całym świecie. Jednak po złożeniu wniosku nie mam pojęcia, do którego procesora płatności zadzwonię. Mógłbym zaprogramować jedną klasę z mnóstwem obudów przełączników, takich jak:

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

Teraz wyobraź sobie, że teraz będziesz musiał utrzymywać cały ten kod w jednej klasie, ponieważ nie jest on prawidłowo odsprzęgnięty, możesz sobie wyobrazić, że dla każdego nowego obsługiwanego procesora będziesz musiał utworzyć nowy przypadek przełączania if // dla przy każdej metodzie staje się to jeszcze bardziej skomplikowane, jednak dzięki zastosowaniu Dependency Injection (lub Inversion of Control - jak to się czasami nazywa, co oznacza, że ​​ktokolwiek kontroluje działanie programu, jest znany tylko w czasie wykonywania, a nie komplikacji), możesz coś osiągnąć bardzo schludne i łatwe w utrzymaniu.

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

** Kod się nie skompiluje, wiem :)

Itai Sagi
źródło
+1, ponieważ wydaje się, że mówisz, że potrzebujesz go tam, gdzie używasz wirtualnych metod / interfejsów. Ale to wciąż rzadkie. Nadal new ThatProcessor()
@ItaiS Możesz uniknąć niezliczonej liczby przełączników dzięki wzorcowemu projektowi klasy. Użyj odbicia System.Reflection.Assembly.GetExecutingAssembly (). CreateInstance ()
domenicr
@domenicr ofcourse! ale chciałem to wyjaśnić w uproszczonym przykładzie
Itai Sagi,
Zgadzam się z powyższym wyjaśnieniem, z wyjątkiem potrzeby klasy fabrycznej. W momencie, gdy wprowadzamy klasę fabryczną, jest to po prostu surowy wybór. Najlepsze wyjaśnienie powyższego znalazłem w rozdziale Poymorphism i funkcji wirtualnej Bruce'a Erkela. Prawdziwa DI powinna być wolna od wyboru, a typ obiektu powinien być wybierany automatycznie w czasie wykonywania przez interfejs. Takie też jest prawdziwe zachowanie polimorficzne.
Arvind Krmar
Na przykład (jak w c ++) mamy wspólny interfejs, który pobiera tylko odwołanie do klasy bazowej i implementuje zachowanie swojej klasy pochodnej bez wyboru. void tune (Instrument & i) {i.play (middleC); } int main () {Flet wiatrowy; melodia (flet); } Instrument jest klasą bazową, z której pochodzi wiatr. Zgodnie z c ++ funkcja wirtualna umożliwia zaimplementowanie zachowania klasy pochodnej poprzez wspólny interfejs.
Arvind Krmar
6

Głównym powodem korzystania z DI jest to, że chcesz ponosić odpowiedzialność za wiedzę dotyczącą implementacji tam, gdzie jest ona dostępna. Idea DI jest bardzo zgodna z enkapsulacją i projektowaniem według interfejsu. Jeśli interfejs wymaga od zaplecza podania pewnych danych, to czy nie ma znaczenia dla interfejsu, w jaki sposób zaplecze rozwiązuje to pytanie. To zależy od modułu obsługi zapytań.

To jest już powszechne w OOP od dłuższego czasu. Wiele razy tworzy fragmenty kodu, takie jak:

I_Dosomething x = new Impl_Dosomething();

Wadą jest to, że klasa implementacji jest wciąż zakodowana na stałe, dlatego ma interfejs, który wie, która implementacja jest używana. DI posuwa projekt o krok dalej, że jedyną rzeczą, którą musi wiedzieć front-end, jest znajomość interfejsu. Pomiędzy DYI i DI znajduje się wzorzec lokalizatora usługi, ponieważ interfejs użytkownika musi dostarczyć klucz (obecny w rejestrze lokalizatora usług), aby umożliwić jego rozpatrzenie. Przykład lokalizatora usług:

I_Dosomething x = ServiceLocator.returnDoing(String pKey);

Przykład DI:

I_Dosomething x = DIContainer.returnThat();

Jednym z wymagań DI jest to, że kontener musi być w stanie dowiedzieć się, która klasa jest implementacją danego interfejsu. Dlatego kontener DI wymaga silnie typowanego projektu i tylko jednej implementacji dla każdego interfejsu w tym samym czasie. Jeśli potrzebujesz więcej implementacji interfejsu w tym samym czasie (jak kalkulator), potrzebujesz lokalizatora usług lub wzorca projektowania fabryki.

D (b) I: Wstrzykiwanie zależności i projektowanie według interfejsu. To ograniczenie nie jest jednak bardzo dużym problemem praktycznym. Zaletą używania D (b) I jest to, że służy on komunikacji między klientem a dostawcą. Interfejs jest perspektywą dla obiektu lub zestawu zachowań. To ostatnie jest tutaj kluczowe.

Wolę administrować umowami o świadczenie usług wraz z kodowaniem D (b) I. Powinny iść razem. Zastosowanie D (b) I jako rozwiązania technicznego bez organizacyjnego zarządzania umowami o świadczenie usług nie jest moim zdaniem bardzo korzystne, ponieważ DI jest wtedy tylko dodatkową warstwą enkapsulacji. Ale jeśli możesz używać go razem z administracją organizacyjną, możesz naprawdę skorzystać z zasady organizacyjnej, którą oferuje D (b) I. Może pomóc w dłuższej perspektywie w ustrukturyzowaniu komunikacji z klientem i innymi działami technicznymi w tematach takich jak testowanie, wersjonowanie i opracowywanie alternatyw. Jeśli masz niejawny interfejs, taki jak w klasie zakodowanej na stałe, jest on z czasem znacznie mniej komunikatywny niż wtedy, gdy wyrazisz go jawnie za pomocą D (b) I. Wszystko sprowadza się do konserwacji, która jest z czasem i nie na raz. :-)

Loek Bergman
źródło
1
„Wadą jest to, że klasa implementacji jest wciąż zakodowana na stałe” <- przez większość czasu jest tylko jedna implementacja i jak powiedziałem, nie mogę wymyślić kodu innej gry, który wymaga interfejsu, który nie jest jeszcze wbudowany (.NET ).
@ acidzombie24 Może być ... ale porównaj wysiłek wdrożenia rozwiązania wykorzystującego DI od początku do wysiłku zmiany rozwiązania innego niż DI później, jeśli potrzebujesz interfejsów. Prawie zawsze wybrałbym pierwszą opcję. Lepiej zapłacić teraz 100 $, niż jutro trzeba zapłacić 100 000 $.
Golo Roden,
1
@GoloRoden Rzeczywiście, utrzymanie jest kluczowym problemem przy użyciu technik takich jak D (b) I. To 80% kosztów aplikacji. Projekt, w którym wymagane zachowanie jest jawnie za pomocą interfejsów od samego początku, oszczędza organizację później na wiele czasu i pieniędzy.
Loek Bergman,
Nie zrozumiem do końca, dopóki nie będę musiał tego zapłacić, ponieważ do tej pory zapłaciłem 0 USD, a do tej pory muszę tylko zapłacić 0 USD. Ale płacę 0,05 $, aby utrzymać każdą linię lub funkcję w czystości.