Kiedy niewłaściwe jest stosowanie wzoru wstrzykiwania zależności?

124

Od czasu uczenia się (i uwielbiania) automatycznych testów, stosuję wzorzec wstrzykiwania zależności w prawie każdym projekcie. Czy zawsze należy stosować ten wzorzec podczas pracy z testami automatycznymi? Czy istnieją sytuacje, w których należy unikać wstrzykiwania zależności?

Tom Squires
źródło
1
Powiązane (prawdopodobnie nie są duplikaty) - stackoverflow.com/questions/2407540/…
Steve314
3
Brzmi trochę jak anty-wzór Golden Hammer. en.wikipedia.org/wiki/Anti-pattern
Cuga

Odpowiedzi:

123

Zasadniczo wstrzykiwanie zależności przyjmuje pewne (zwykle, ale nie zawsze poprawne) założenia dotyczące natury twoich obiektów. Jeśli są one błędne, DI może nie być najlepszym rozwiązaniem:

  • Po pierwsze, najbardziej zasadniczo, DI zakłada, że ​​ścisłe sprzężenie implementacji obiektów ZAWSZE jest złe . Taka jest istota zasady odwrócenia zależności: „nigdy nie należy uzależniać się od konkrecji; tylko od abstrakcji”.

    To zamyka zależny obiekt do zmiany na podstawie zmiany konkretnej implementacji; klasa zależna od ConsoleWriter będzie musiała się zmienić, jeśli dane wyjściowe będą musiały przejść do pliku, ale jeśli klasa była zależna tylko od IWriter ujawniającego metodę Write (), możemy zastąpić ConsoleWriter aktualnie używaną FileWriter i naszym klasa zależna nie znałaby różnicy (Zasada podstawienia Liskhova).

    Jednak projekt NIGDY nie może być zamknięty na wszystkie rodzaje zmian; jeśli zmienia się konstrukcja samego interfejsu IWriter, aby dodać parametr do Write (), należy teraz zmienić dodatkowy obiekt kodowy (interfejs IWriter), nad obiektem / metodą implementacji i jego wykorzystaniem. Jeśli zmiany w rzeczywistym interfejsie są bardziej prawdopodobne niż zmiany w implementacji wspomnianego interfejsu, luźne sprzężenie (i zależności luźno sprzężone DI) mogą powodować więcej problemów niż rozwiązuje.

  • Po drugie, DI i zakłada, że ​​klasa zależna NIGDY nie jest dobrym miejscem do stworzenia zależności . Odnosi się to do zasady jednolitej odpowiedzialności; jeśli masz kod, który tworzy zależność i również z niej korzysta, istnieją dwa powody, dla których klasa zależna może być zmuszona do zmiany (zmiana użycia LUB implementacji), naruszając SRP.

    Ponownie jednak dodanie warstw pośrednich dla DI może być rozwiązaniem problemu, który nie istnieje; jeśli logiczne jest zawarcie logiki w zależności, ale ta logika jest jedyną taką implementacją zależności, wówczas bardziej bolesne jest kodowanie luźno sprzężonego rozwiązania zależności (wstrzyknięcie, lokalizacja usługi, fabryka) niż byłoby użyć newi zapomnieć o tym.

  • Wreszcie, DI ze swej natury centralizuje wiedzę o wszystkich zależnościach i ich implementacjach . Zwiększa to liczbę referencji, które musi posiadać zespół wykonujący wstrzyknięcie, aw większości przypadków NIE zmniejsza liczby referencji wymaganych przez rzeczywiste zestawy klas zależnych.

    COŚ, GDZIEŚ, musi mieć wiedzę na temat zależności, interfejsu zależności i implementacji zależności, aby „połączyć kropki” i zaspokoić tę zależność. DI ma tendencję do umieszczania całej tej wiedzy na bardzo wysokim poziomie, albo w kontenerze IoC, albo w kodzie tworzącym „główne” obiekty, takie jak główna postać lub kontroler, które muszą uwodnić (lub zapewnić metody fabryczne) zależności. Może to umieścić wiele koniecznie ściśle powiązanych kodów i wiele odniesień do asemblera na wysokich poziomach twojej aplikacji, która potrzebuje tylko tej wiedzy, aby „ukryć” ją przed rzeczywistymi klasami zależnymi (która z bardzo podstawowej perspektywy jest najlepsze miejsce na posiadanie tej wiedzy; gdzie jest używana).

    Zwykle również nie usuwa wspomnianych odniesień z niższej części kodu; osoba zależna musi nadal odwoływać się do biblioteki zawierającej interfejs ze względu na jej zależność, która znajduje się w jednym z trzech miejsc:

    • wszystko w jednym zestawie „Interfejsy”, który staje się bardzo zorientowany na aplikacje,
    • każdy z nich obok podstawowych implementacji, eliminując zaletę konieczności ponownej kompilacji zależności w przypadku zmiany zależności lub
    • jeden lub dwa elementy w bardzo spójnych zestawach, co powoduje zwiększenie liczby zestawów, radykalnie zwiększa czasy „pełnej kompilacji” i zmniejsza wydajność aplikacji.

    Wszystko to ponownie rozwiązuje problem w miejscach, w których może ich nie być.

KeithS
źródło
1
SRP = zasada pojedynczej odpowiedzialności, dla każdego, kto się zastanawia.
Theodore Murdock
1
Tak, a LSP jest zasadą substytucji Liskowa; biorąc pod uwagę zależność od A, zależność ta powinna być w stanie spełnić B, która wywodzi się z A bez żadnych innych zmian.
KeithS
wstrzyknięcie zależności szczególnie pomaga tam, gdzie trzeba uzyskać klasę A z klasy B z klasy D itp. w takim przypadku (i tylko w tym przypadku) środowisko DI może generować mniej wzdęć asemblacyjnych niż wstrzyknięcie biednego człowieka. także nigdy nie miałem wąskiego gardła spowodowanego przez DI .. myślę o kosztach utrzymania, nigdy o kosztach procesora, ponieważ kod zawsze można zoptymalizować, ale robienie tego bez powodu ma koszt
GameDeveloper
Czy innym założeniem byłoby „jeśli zależność zmienia się, zmienia się wszędzie”? W każdym razie musisz spojrzeć na wszystkie konsumujące klasy
Richard Tingle,
3
Ta odpowiedź nie odnosi się do wpływu testowania wstrzykiwania zależności w porównaniu do ich twardego kodowania. Zobacz także zasadę jawnych zależności ( deviq.com/explicit-dependencies-principle )
ssmith
30

Poza strukturami wstrzykiwania zależności, wstrzykiwanie zależności (poprzez wstrzykiwanie konstruktora lub wstrzykiwanie settera) jest prawie grą o sumie zerowej: zmniejszasz sprzężenie między obiektem A i jego zależnością B, ale teraz każdy obiekt, który potrzebuje wystąpienia A, musi teraz również skonstruuj obiekt B.

Nieznacznie zmniejszyłeś sprzężenie między A i B, ale zmniejszyłeś hermetyzację A i zwiększyłeś sprzężenie między A i dowolną klasą, która musi zbudować instancję A, poprzez sprzężenie ich również z zależnościami A.

Zatem wstrzykiwanie zależności (bez frameworka) jest równie szkodliwe, jak pomocne.

Dodatkowy koszt jest często łatwo uzasadniony: jeśli kod klienta wie więcej o tym, jak zbudować zależność niż sam obiekt, to wstrzyknięcie zależności naprawdę zmniejsza sprzężenie; na przykład skaner nie wie dużo o tym, jak uzyskać lub skonstruować strumień wejściowy do analizy danych wejściowych, ani z jakiego źródła kod klienta chce analizować dane wejściowe, więc wstrzyknięcie konstruktora strumienia wejściowego jest oczywistym rozwiązaniem.

Testowanie jest kolejnym uzasadnieniem, aby móc użyć próbnych zależności. Powinno to oznaczać dodanie dodatkowego konstruktora używanego tylko do testowania, który pozwala wstrzykiwać zależności: jeśli zamiast tego zmienisz konstruktory tak, aby zawsze wymagały wstrzykiwania zależności, nagle musisz wiedzieć o zależnościach zależności w celu zbudowania swojej bezpośrednie zależności i nie można wykonać żadnej pracy.

Może to być pomocne, ale zdecydowanie powinieneś zadać sobie pytanie o każdą zależność, czy korzyść z testowania jest warta kosztu i czy naprawdę będę chciał kpić z tej zależności podczas testowania?

Po dodaniu struktury wstrzykiwania zależności, a konstrukcja zależności jest delegowana nie do kodu klienta, ale do struktury, analiza kosztów / korzyści zmienia się znacznie.

W ramach wstrzykiwania zależności kompromisy są nieco inne; to, co tracisz przez wstrzyknięcie zależności, to możliwość łatwego dowiedzenia się, na której implementacji się opierasz, i przeniesienie odpowiedzialności za podjęcie decyzji o zależności, na której polegasz, na proces automatycznego rozwiązywania problemów (np. jeśli potrzebujemy Foo @ Inject'ed , musi być coś, co @Provides Foo i którego wstrzyknięte zależności są dostępne), lub jakiś plik konfiguracyjny wysokiego poziomu, który określa, jakiego dostawcę należy użyć dla każdego zasobu, lub jakiejś hybrydy tych dwóch (na przykład może istnieć być zautomatyzowanym procesem rozwiązywania zależności, które w razie potrzeby można zastąpić za pomocą pliku konfiguracyjnego).

Podobnie jak w przypadku iniekcji konstruktora, myślę, że korzyść z tego jest znowu bardzo podobna do kosztu: nie musisz wiedzieć, kto dostarcza dane, na których polegasz, a jeśli istnieje wiele potencjalnych dostawców, nie musisz znać preferowanej kolejności sprawdzania dostawców, upewnij się, że każda lokalizacja, która potrzebuje kontroli danych dla wszystkich potencjalnych dostawców itp., ponieważ wszystko jest obsługiwane na wysokim poziomie przez wstrzyknięcie zależności Platforma.

Chociaż osobiście nie mam dużego doświadczenia z frameworkami DI, mam wrażenie, że zapewniają one więcej korzyści niż kosztów, gdy kłopot ze znalezieniem właściwego dostawcy potrzebnych danych lub usług jest wyższy niż ból głowy, gdy coś zawiedzie, nie wiedząc od razu, jaki kod dostarczył złe dane, co spowodowało późniejszą awarię kodu.

W niektórych przypadkach inne wzorce, które zaciemniają zależności (np. Lokalizatory usług), zostały już przyjęte (a być może także udowodniły swoją wartość), gdy na scenie pojawiły się frameworki DI, a frameworki DI zostały przyjęte, ponieważ oferowały pewną przewagę konkurencyjną, na przykład wymagały mniej kodu podstawowego lub potencjalnie robiąc mniej, aby ukryć dostawcę zależności, gdy konieczne będzie ustalenie, który dostawca faktycznie jest w użyciu.

Theodore Murdock
źródło
6
Wystarczy krótki komentarz na temat kpienia z zależności w testach i „musisz wiedzieć o zależnościach zależności”… To wcale nie jest prawda. Nie trzeba wiedzieć ani przejmować się niektórymi konkretnymi zależnościami implementacyjnymi, jeśli wstrzykuje się próbkę. Trzeba tylko wiedzieć o bezpośrednich zależnościach testowanej klasy, które są zaspokojone przez próbę (y).
Eric King
6
Nie rozumiesz, nie mówię o tym, kiedy wstrzykujesz próbkę, mówię o prawdziwym kodzie. Rozważmy klasę A z zależnością B, która z kolei ma zależność C, która z kolei ma zależność D. Bez DI, A konstruuje B, B konstruuje C, C konstruuje D. Aby wstrzyknąć konstrukcję, aby zbudować A, musisz najpierw zbudować B, aby skonstruować B, musisz najpierw skonstruować C, a aby zbudować C, najpierw musisz skonstruować D. Zatem klasa A musi teraz wiedzieć o D, zależności zależności zależnej, aby zbudować B. To prowadzi do nadmiernego sprzężenia.
Theodore Murdock
1
Dodatkowy konstruktor użyty do testowania nie wymagałby tak dużych kosztów, aby umożliwić wstrzyknięcie zależności. Spróbuję skorygować to, co powiedziałem.
Theodore Murdock
3
Wstrzykiwanie zależności nie zmienia tylko zależności w grze o sumie zerowej. Przenosisz swoje zależności na miejsca, które już istnieją: główny projekt. Możesz nawet zastosować podejście oparte na konwencjach, aby upewnić się, że zależności są rozwiązywane tylko w czasie wykonywania, na przykład poprzez określenie, które klasy są konkretne według przestrzeni nazw lub cokolwiek innego. Muszę powiedzieć, że to naiwna odpowiedź
Sprague,
2
Zastrzyk @Sprague Dependency porusza się wokół zależności w grze o nieco ujemnej sumie, jeśli zastosujesz go w przypadkach, w których nie powinien być zastosowany. Celem tej odpowiedzi jest przypomnienie ludziom, że wstrzykiwanie zależności nie jest z natury dobre, jest to wzorzec projektowy, który jest niezwykle przydatny w odpowiednich okolicznościach, ale nieuzasadniony koszt w normalnych okolicznościach (przynajmniej w domenach programowania, w których pracowałem , Rozumiem, że w niektórych domenach jest to częściej przydatne niż w innych). DI czasami staje się modnym hasłem i celem samym w sobie, co nie ma sensu.
Theodore Murdock
11
  1. jeśli tworzysz jednostki bazy danych, powinieneś raczej mieć klasę fabryczną, którą zamiast tego wstrzykniesz do kontrolera,

  2. jeśli chcesz stworzyć prymitywne obiekty, takie jak ints lub longs. Powinieneś także utworzyć „ręcznie” większość standardowych obiektów bibliotecznych, takich jak daty, przewodniki itp.

  3. jeśli chcesz wstrzykiwać ciągi konfiguracji, prawdopodobnie lepiej jest wstrzyknąć niektóre obiekty konfiguracji (ogólnie zaleca się zawijanie prostych typów w znaczące obiekty: int temperatureInCelsiusDegrees -> temperatura CelciusDeegree)

I nie używaj Lokalizatora usług jako alternatywy wstrzykiwania zależności, jest to anty-wzorzec, więcej informacji: http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx

0lukasz0
źródło
Wszystkie punkty dotyczą używania różnych form iniekcji, a nie całkowitego ich używania. +1 za link.
Jan Hudec
3
Lokalizator usług NIE jest anty-wzorcem, a facet, który blog, do którego prowadzi link, nie jest ekspertem w zakresie wzorców. To, że zyskał sławę StackExchange, jest pewnego rodzaju niedogodnością w projektowaniu oprogramowania. Martin Fowler (autor podstawowych wzorców architektury aplikacji korporacyjnych) ma bardziej rozsądne zdanie na ten temat: martinfowler.com/articles/injection.html
Colin
1
@Colin, wręcz przeciwnie, Service Locator to zdecydowanie anty-wzorzec, a tutaj w skrócie jest to powód .
Kyralessa
@ Kyralessa - zdajesz sobie sprawę, że platformy DI wewnętrznie używają wzorca Service Locator do wyszukiwania usług, prawda? Są okoliczności, w których oba są odpowiednie i żaden z nich nie jest anty-wzorcem pomimo nienawiści StackExchange, którą niektórzy chcą zrzucić.
Colin
Jasne, a mój samochód wewnętrznie używa tłoków i świec zapłonowych oraz wielu innych rzeczy, które nie są jednak dobrym interfejsem dla zwykłego człowieka, który po prostu chce prowadzić samochód.
Kyralessa
8

Gdy nie możesz nic zyskać, czyniąc projekt możliwym do utrzymania i przetestowania.

Poważnie, uwielbiam ogólnie IoC i DI i powiedziałbym, że 98% czasu będę używać tego wzorca bezbłędnie. Jest to szczególnie ważne w środowisku wielu użytkowników, w którym kod może być wielokrotnie wykorzystywany przez różnych członków zespołu i różne projekty, ponieważ oddziela logikę od implementacji. Rejestrowanie jest tego najlepszym przykładem. Interfejs ILog wprowadzony do klasy jest tysiąc razy łatwiejszy w utrzymaniu niż zwykłe podłączenie twojego środowiska rejestrowania-du-Jour, ponieważ nie masz gwarancji, że inny projekt użyje tego samego środowiska rejestrowania (jeśli używa jeden w ogóle!).

Są jednak chwile, kiedy nie jest to odpowiedni wzór. Na przykład funkcjonalne punkty wejścia, które są implementowane w kontekście statycznym przez nieprzekraczalny inicjator (WebMethods, patrzę na ciebie, ale twoja metoda Main () w klasie Program jest innym przykładem) po prostu nie może mieć wstrzykniętych zależności przy inicjalizacji czas. Chciałbym również powiedzieć, że prototyp lub dowolny wyrzucony fragment kodu śledczego jest również złym kandydatem; korzyści z DI to w przybliżeniu korzyści średnio- i długoterminowe (testowalność i łatwość konserwacji), jeśli jesteś pewien, że wyrzucisz większość kodu w ciągu tygodnia lub mniej więcej powiedziałbym, że nic nie zyskasz izolując zależności, po prostu spędzaj czas, który normalnie spędzasz na testowaniu i izolowaniu zależności, aby uruchomić kod.

Podsumowując, rozsądne jest przyjęcie pragmatycznego podejścia do dowolnej metodologii lub schematu, ponieważ nic nie ma zastosowania w 100% przypadków.

Jedną z rzeczy, na które należy zwrócić uwagę, jest twój komentarz na temat testów automatycznych: moja definicja tego to zautomatyzowane testy funkcjonalne , na przykład skrypty selenu, jeśli jesteś w kontekście sieci. Są to na ogół testy całkowicie czarnej skrzynki, bez potrzeby wiedzieć o wewnętrznych działaniach kodu. Jeśli miałeś na myśli testy jednostkowe lub integracyjne, powiedziałbym, że wzorzec DI prawie zawsze ma zastosowanie do każdego projektu, który w dużej mierze opiera się na tego rodzaju testach białych skrzynek, ponieważ na przykład pozwala testować takie rzeczy, jak metody dotykające DB bez potrzeby obecności DB.

Ed James
źródło
Nie rozumiem, co masz na myśli mówiąc o logowaniu. Z pewnością wszystkie programy rejestrujące nie będą zgodne z tym samym interfejsem, więc musisz napisać własne opakowanie, aby przetłumaczyć sposób logowania między projektami a konkretnym programem rejestrującym (zakładając, że chcesz mieć możliwość łatwej zmiany programów rejestrujących). Co potem daje ci DI?
Richard Tingle,
@RichardTingle Powiedziałbym, że powinieneś zdefiniować swoje opakowanie rejestrowania jako interfejs, a następnie napisać lekką implementację tego interfejsu dla każdej zewnętrznej usługi rejestrowania, zamiast używać pojedynczej klasy rejestrowania, która otacza wiele abstrakcji. Jest to ta sama koncepcja, którą proponujesz, ale jest ona abstrakcyjna na innym poziomie.
Ed James
1

Podczas gdy inne odpowiedzi koncentrują się na aspektach technicznych, chciałbym dodać praktyczny wymiar.

Przez lata doszedłem do wniosku, że istnieje kilka praktycznych wymagań, które należy spełnić, aby wprowadzenie Dependency Injection miało odnieść sukces.

  1. Powód powinien być uzasadniony.

    Wydaje się to oczywiste, ale jeśli twój kod pobiera tylko rzeczy z bazy danych i zwraca je bez logiki, to dodanie kontenera DI sprawia, że ​​rzeczy są bardziej złożone i nie przynoszą rzeczywistych korzyści. Ważniejsze byłyby tutaj testy integracyjne.

  2. Zespół musi być przeszkolony i znajdować się na pokładzie.

    Chyba że większość członków zespołu jest na pokładzie i nie rozumie, że dodanie odwrócenia kontenera sterującego staje się jeszcze jednym sposobem na zrobienie czegoś i jeszcze bardziej komplikuje bazę kodu.

    Jeśli DI zostanie wprowadzony przez nowego członka zespołu, ponieważ rozumieją go i podoba im się, a po prostu chcą pokazać, że są dobrzy, ORAZ zespół nie jest aktywnie zaangażowany, istnieje realne ryzyko, że faktycznie obniży jakość kod.

  3. Musisz przetestować

    Podczas gdy odsprzęganie jest ogólnie dobrą rzeczą, DI może przenosić rozdzielczość zależności z czasu kompilacji do czasu wykonywania. Jest to dość niebezpieczne, jeśli nie przetestujesz dobrze. Błędy rozwiązywania w czasie wykonywania mogą być kosztowne w śledzeniu i rozwiązywaniu.
    (Z testu wynika, że ​​to robisz, ale wiele zespołów nie testuje w zakresie wymaganym przez DI.)

tymtam
źródło
1

To nie jest pełna odpowiedź, ale tylko kolejny punkt.

Gdy masz aplikację, która uruchamia się raz, działa przez długi czas (jak aplikacja internetowa) DI może być dobry.

Jeśli masz aplikację, która uruchamia się wiele razy i działa krócej (np. Aplikacja mobilna), prawdopodobnie nie chcesz kontenera.

Koray Tugay
źródło
Nie rozumiem, jak czas na żywo aplikacji związany z DI
Michael Freidgeim
@MichaelFreidgeim Inicjowanie kontekstu zajmuje trochę czasu, zwykle pojemniki DI są dość ciężkie, takie jak Wiosna. Stwórz aplikację hello world z tylko jedną klasą i stwórz ją ze Springem i zacznij 10 razy, a zobaczysz, co mam na myśli.
Koray Tugay
Brzmi jak problem z pojedynczym pojemnikiem DI. W świecie .Net nie słyszałem o czasie inicjalizacji jako istotnym problemie dla pojemników DI
Michael Freidgeim
1

Spróbuj użyć podstawowych zasad OOP: użyj dziedziczenia, aby wyodrębnić wspólną funkcjonalność, obudować (ukryć) rzeczy, które powinny być chronione przed światem zewnętrznym za pomocą prywatnych / wewnętrznych / chronionych członków / typów. Użyj dowolnego zaawansowanego środowiska testowego, aby wstrzyknąć kod tylko do testów, na przykład https://www.typemock.com/ lub https://www.telerik.com/products/mocking.aspx .

Następnie spróbuj ponownie napisać go za pomocą DI i porównać kod, co zwykle zobaczysz za pomocą DI:

  1. Masz więcej interfejsów (więcej typów)
  2. Utworzyłeś duplikaty podpisów metod publicznych i będziesz musiał go dwukrotnie utrzymywać (nie możesz po prostu zmienić jednego parametru raz, musisz to zrobić dwa razy, w zasadzie wszystkie możliwości refaktoryzacji i nawigacji stają się bardziej skomplikowane)
  3. Przesunąłeś niektóre błędy kompilacji do błędów w czasie wykonywania (z DI możesz po prostu zignorować pewną zależność podczas kodowania i nie na pewno będzie to ujawnione podczas testowania)
  4. Otworzyliście swoją enkapsulację. Teraz członkowie chronieni, typy wewnętrzne itp. Stają się publiczne
  5. Ogólna ilość kodu została zwiększona

Powiedziałbym, że z tego, co widziałem, prawie zawsze jakość kodu spada z DI.

Jeśli jednak użyjesz tylko „publicznego” modyfikatora dostępu w deklaracji klasy i / lub publicznych / prywatnych modyfikatorów dla członków i / lub nie masz wyboru, aby kupić drogie narzędzia testowe i jednocześnie potrzebujesz testów jednostkowych, które mogą „ zostać zastąpiony testami integracyjnymi i / lub masz już interfejsy dla klas, które zamierzasz wprowadzić, DI to dobry wybór!

ps prawdopodobnie dostanę wiele minusów za ten post, wierzę, ponieważ większość współczesnych programistów po prostu nie rozumie, jak i dlaczego używać wewnętrznego słowa kluczowego i jak zmniejszyć sprzężenie twoich komponentów i wreszcie dlaczego je zmniejszyć) w końcu, po prostu spróbuj kodować i porównywać

Eugene Gorbovoy
źródło
0

Alternatywą dla Dependency Injection używa lokalizatora usług . Lokalizator usług jest łatwiejszy do zrozumienia, debugowania i upraszcza konstruowanie obiektu, zwłaszcza jeśli nie używasz środowiska DI. Lokalizatory usług są dobrym wzorcem do zarządzania zewnętrznymi zależnościami statycznymi , na przykład bazą danych, którą w innym przypadku musiałbyś przekazać do każdego obiektu w warstwie dostępu do danych.

Podczas refaktoryzacji starszego kodu często łatwiej jest refaktoryzować do lokalizatora usług niż do wstrzykiwania zależności. Wystarczy zastąpić instancje przeglądami usług, a następnie sfałszować usługę w teście jednostkowym.

Istnieją jednak pewne wady Lokalizatora usług. Znajomość zależności klasy jest trudniejsza, ponieważ zależności są ukryte w implementacji klasy, a nie w konstruktorach lub ustawieniach. A tworzenie dwóch obiektów, które opierają się na różnych implementacjach tej samej usługi, jest trudne lub niemożliwe.

TLDR : Jeśli klasa ma zależności statyczne lub refaktoryzuje starszy kod, Lokalizator usług jest prawdopodobnie lepszy niż DI.

Garrett Hall
źródło
12
Lokalizator usług ukrywa zależności w kodzie . Nie jest to dobry wzór do użycia.
Kyralessa
Jeśli chodzi o zależności statyczne, wolałbym raczej widzieć na nich fasady, które implementują interfejsy. Mogą być następnie wstrzykiwane w zależności i spadają na ciebie granat statyczny.
Erik Dietrich
@ Kyralessa Zgadzam się, lokalizator usług ma wiele wad i DI jest prawie zawsze lepsza. Uważam jednak, że istnieje kilka wyjątków od reguły, podobnie jak w przypadku wszystkich zasad programowania.
Garrett Hall
Główne zaakceptowane użycie lokalizacji usługi jest zgodne ze wzorcem strategii, w którym klasa „wyboru strategii” ma wystarczającą logikę, aby znaleźć strategię do użycia, albo oddać ją z powrotem, albo przekazać połączenie. Nawet w tym przypadku selektor strategii może być fasadą dla metody fabrycznej zapewnianej przez kontener IoC, któremu przypisano tę samą logikę; powodem, dla którego chcesz to przełamać, jest umieszczenie logiki tam, gdzie należy, a nie tam, gdzie jest najbardziej ukryta.
KeithS
Cytując Martina Fowlera: „Wybór pomiędzy (DI a Service Locator) jest mniej ważny niż zasada oddzielenia konfiguracji od użycia”. Pomysł, że DI jest ZAWSZE lepszy, to wieprzowina. Jeśli usługa jest używana globalnie, korzystanie z DI jest bardziej kłopotliwe i kruche. Ponadto DI jest mniej korzystne dla architektur wtyczek, w których implementacje nie są znane do czasu uruchomienia. Pomyśl, jak niewygodne byłoby zawsze przekazywanie dodatkowych argumentów do Debug.WriteLine () i tym podobnych!
Colin