Jakie są wady korzystania z Dependency Injection? [Zamknięte]

336

Próbuję wprowadzić DI jako wzorzec tutaj w pracy, a jeden z naszych głównych programistów chciałby wiedzieć: Jakie - jeśli w ogóle - są wady stosowania wzorca Dependency Injection?

Uwaga: Szukam tutaj - jeśli to możliwe - wyczerpującej listy, a nie subiektywnej dyskusji na ten temat.


Wyjaśnienie : Mówię o wzorze wstrzykiwania zależności (patrz ten artykuł autorstwa Martina Fowlera), a nie konkretnym frameworku, czy to opartym na XML (takim jak Spring), czy opartym na kodzie (takim jak Guice), czy też „self-rolled” .


Edycja : Trwa świetna dalsza dyskusja / ranting / debata / r / programowanie tutaj.

Epaga
źródło
Dobrym pomysłem może być określenie, czy mamy dyskutować o samym DI, czy o konkretnym typie narzędzia, które go obsługuje (oparte na XML, czy nie).
Jesse Millikan,
5
Różnica polega na tym, że NIE szukam odpowiedzi, które odnoszą się tylko do określonych ram, takich jak „XML sucks”. :) Szukam odpowiedzi, które odnoszą się do koncepcji DI opisanej przez Fowlera tutaj: martinfowler.com/articles/injection.html
Epaga
3
W ogóle nie widzę wad dla DI (konstruktor, setter lub metoda). Odsprzęga i upraszcza bez kosztów ogólnych.
Kissaki,
2
@kissaki oprócz setera wstrzykuje rzeczy. Najlepiej robić przedmioty, które nadają się do budowy. Jeśli mi nie wierzysz, po prostu spróbuj dodać innego współpracownika do obiektu z zastrzykiem „ustawiającym”, na pewno dostaniesz NPE ....
time4tea

Odpowiedzi:

209

Kilka punktów:

  • DI zwiększa złożoność, zwykle poprzez zwiększenie liczby klas, ponieważ obowiązki są bardziej rozdzielone, co nie zawsze jest korzystne
  • Twój kod będzie (nieco) sprzężony ze strukturą wstrzykiwania zależności, której używasz (lub bardziej ogólnie, w jaki sposób zdecydujesz się zaimplementować wzorzec DI)
  • Kontenery DI lub podejścia, które wykonują rozpoznawanie typów, generalnie ponoszą niewielką karę czasu działania (bardzo nieznaczne, ale tam są)

Zasadniczo korzyść z oddzielenia powoduje, że każde zadanie jest łatwiejsze do odczytania i zrozumienia, ale zwiększa złożoność organizacji bardziej złożonych zadań.

Håvard S.
źródło
72
- Rozdzielenie klas zmniejsza złożoność. Wiele klas nie czyni aplikacji skomplikowanymi. - Powinieneś mieć zależność tylko od swojego środowiska DI w katalogu głównym aplikacji.
Robert
91
Jesteśmy ludźmi; nasza pamięć krótkotrwała jest ograniczona i nie może obsługiwać wielu <xxx> jednocześnie. Jest tak samo w przypadku klas, jak w przypadku metod, funkcji, plików lub innych konstrukcji używanych do tworzenia programu.
Håvard S
45
@Havard S: Tak, ale 5 naprawdę złożonych klas nie jest prostszych niż 15 prostych. Sposobem na zmniejszenie złożoności jest zmniejszenie zestawu roboczego wymaganego przez programistę pracującego na kodzie - złożone, wysoce współzależne klasy tego nie osiągają.
Kyoryu
35
@kyoryu Myślę, że wszyscy się z tym zgadzamy. Pamiętaj, że złożoność to nie to samo, co sprzężenie . Zmniejszenie sprzężenia może zwiększyć złożoność. Naprawdę nie twierdzę, że DI jest złą rzeczą, ponieważ zwiększa liczbę klas, podkreślam potencjalne wady z tym związane. :)
Håvard S
96
+1 dla „DI zwiększa złożoność” To prawda w DI i poza nią. (Niemal) za każdym razem, gdy zwiększamy elastyczność, zwiększymy złożoność. Chodzi o równowagę, która zaczyna się od poznania zalet i wad. Kiedy ludzie mówią: „nie ma wad”, jest to pewny wskaźnik, że jeszcze nie w pełni to zrozumieli.
Don Branson,
183

Ten sam podstawowy problem, który często napotykasz z programowaniem obiektowym, regułami stylu i wszystkim innym. Możliwe jest - bardzo powszechne - zbytnie abstrakowanie i dodawanie zbyt dużej liczby pośrednich oraz generalne stosowanie dobrych technik w nadmiernych i niewłaściwych miejscach.

Każdy zastosowany wzór lub inny konstrukt powoduje złożoność. Abstrakcja i pośrednie rozpraszają informacje, czasami usuwając nieistotne szczegóły z drogi, ale równie często czasami utrudniają dokładne zrozumienie, co się dzieje. Każda zastosowana reguła zapewnia elastyczność, wykluczając opcje, które mogą być najlepszym rozwiązaniem.

Chodzi o to, aby napisać kod, który wykonuje zadanie i jest solidny, czytelny i łatwy w utrzymaniu. Jesteś programistą, a nie budowniczym wieży z kości słoniowej.

Ważne linki

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


Prawdopodobnie najprostszą formą wstrzykiwania zależności (nie śmiać się) jest parametr. Kod zależny jest zależny od danych, a dane te są wprowadzane poprzez przekazanie parametru.

Tak, to głupie i nie odnosi się do zorientowanego obiektowo punktu wstrzykiwania zależności, ale programista funkcjonalny powie ci, że (jeśli masz funkcje pierwszej klasy) jest to jedyny rodzaj zastrzyku zależności, którego potrzebujesz. Chodzi o to, aby wziąć trywialny przykład i pokazać potencjalne problemy.

Weźmy tę prostą tradycyjną funkcję - składnia C ++ nie ma tutaj znaczenia, ale muszę to jakoś przeliterować ...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

Mam zależność, którą chcę wyodrębnić i wstrzyknąć - tekst „Hello World”. Wystarczająco łatwe...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

Jak to jest bardziej nieelastyczne niż oryginał? Co jeśli zdecyduję, że wyjście powinno być Unicode. Prawdopodobnie chcę zmienić std :: cout na std :: wcout. Ale to oznacza, że ​​moje struny muszą być wchar_t, a nie char. Albo każdy program wywołujący musi zostać zmieniony, albo (bardziej rozsądnie), stara implementacja zostaje zastąpiona adapterem, który tłumaczy ciąg znaków i wywołuje nową implementację.

To prace konserwacyjne, które nie byłyby potrzebne, gdybyśmy zachowali oryginał.

A jeśli wydaje się to trywialne, spójrz na tę rzeczywistą funkcję z Win32 API ...

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

To 12 „zależności”, z którymi trzeba sobie poradzić. Na przykład, jeśli rozdzielczości ekranu stają się naprawdę ogromne, być może będziemy potrzebować 64-bitowych wartości współrzędnych - i innej wersji CreateWindowEx. I tak, wciąż kręci się starsza wersja, która prawdopodobnie została zmapowana do nowszej wersji za kulisami ...

http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

Te „zależności” nie są tylko problemem dla pierwotnego programisty - każdy, kto korzysta z tego interfejsu, musi sprawdzić, jakie są zależności, jak są określone i co oznaczają, i dowiedzieć się, co zrobić dla swojej aplikacji. To tutaj słowa „rozsądne wartości domyślne” mogą znacznie uprościć życie.

Zastrzykiwanie zależności obiektowych zasadniczo nie różni się. Pisanie klasy jest narzutem, zarówno w tekście kodu źródłowego, jak i w czasie programowania, a jeśli klasa ta jest zapisywana w celu dostarczania zależności zgodnie z niektórymi specyfikacjami obiektów zależnych, wówczas obiekt zależny jest zablokowany do obsługi tego interfejsu, nawet jeśli jest taka potrzeba zastąpić implementację tego obiektu.

Nic z tego nie powinno być interpretowane jako twierdzenie, że wstrzykiwanie zależności jest złe - wręcz przeciwnie. Ale każdą dobrą technikę można zastosować nadmiernie i w niewłaściwym miejscu. Podobnie jak nie każdy ciąg musi zostać wyodrębniony i przekształcony w parametr, nie każde zachowanie niskiego poziomu musi zostać wyodrębnione z obiektów wysokiego poziomu i przekształcone w zależność do wstrzykiwania.

Steve314
źródło
3
Wstrzykiwanie zależności nie ma żadnej z tych wad. Zmienia tylko sposób przekazywania zależności do obiektów, co nie dodaje złożoności ani elastyczności. Wręcz przeciwnie.
Kissaki,
24
@Kissaki - tak, zmienia sposób przekazywania zależności. I musisz napisać kod, aby obsłużyć to przekazywanie zależności. A zanim to zrobisz, musisz ustalić, które zależności przekazać, zdefiniować je w sposób, który ma sens dla kogoś, kto nie koduje kodu zależnego itp. Możesz uniknąć wpływu w czasie wykonywania (np. Używając parametrów zasad do szablonów w C ++), ale nadal jest to kod, który musisz napisać i zachować. Jeśli jest to uzasadnione, jest to bardzo niska cena za dużą nagrodę, ale jeśli udajesz, że nie ma ceny do zapłacenia, oznacza to, że będziesz płacić tę cenę, gdy nie będzie zysku.
Steve314,
6
@Kissaki - jeśli chodzi o nieelastyczność, kiedy już określisz swoje zależności, jesteś w to uwięziony - inni ludzie zapisali zależności, aby wstrzyknąć zgodnie z tą specyfikacją. Jeśli potrzebujesz nowej implementacji kodu zależnego, która wymaga nieco innych zależności - trudne, jesteś zamknięty. Czas zamiast tego zacząć pisać adaptery dla tych zależności (i nieco więcej narzutu i kolejnej warstwy abstrakcji).
Steve314,
Nie jestem pewien, czy rozumiem ten przykład, jeśli rozważasz przeciążenie. Aby wyświetlić dosłowny „Hello World”, używamy podpisu (). Aby wyprowadzić 8-bitowy ciąg, użyj podpisu (char). Aby wyprowadzić Unicode, przeciążenie (wchar_t). Oczywiście w tym przypadku (wchar_t) to ten, który inni nazywają za kulisami. Nie trzeba tam dużo przepisywać kodu. Czy coś brakuje?
ingredient_15939
2
@ component_15939 - tak, to jest uproszczony przykład zabawki. Jeśli tak naprawdę to robisz, wystarczy użyć przeciążenia zamiast adaptera, ponieważ koszt duplikacji kodu jest mniejszy niż koszt adaptera. Należy jednak pamiętać, że powielanie kodu jest na ogół złą rzeczą, a używanie adapterów w celu uniknięcia powielania kodu to kolejna dobra technika (która czasami może być używana zbyt często i w niewłaściwych miejscach).
Steve314
77

Oto moja pierwsza reakcja: w zasadzie te same wady dowolnego wzoru.

  • nauka wymaga czasu
  • źle zrozumiany może przynieść więcej szkody niż pożytku
  • jeśli doprowadzi się do skrajności, może to wymagać więcej pracy niż uzasadniałoby to korzyść
Epaga
źródło
2
Dlaczego nauka wymaga czasu? Pomyślałem, że to czyni sprawę prostszą w porównaniu z ejb.
fastcodejava,
8
Wydaje się to faliste, biorąc pod uwagę, że nie chcesz subiektywnej dyskusji. Proponuję zbudować (zbiorczo, jeśli to konieczne) realistyczny przykład do zbadania. Zbudowanie próbki bez DI byłoby dobrym punktem wyjścia. Następnie moglibyśmy rzucić wyzwanie zmianom wymagań i zbadać wpływ. (Nawiasem mówiąc, jest mi bardzo wygodnie zasugerować, że idę spać.)
Jesse Millikan,
4
To naprawdę potrzebuje kilku przykładów na poparcie tego, a po nauce dziesiątek osób mogę powiedzieć, że naprawdę bardzo prosta jest nauka i rozpoczęcie wdrażania.
chillitom
4
Tak naprawdę nie uważam DI za wzór. To (w połączeniu z IoC) jest w rzeczywistości bardziej modelem programowania - jeśli w pełni je zastosujesz, twój kod będzie wyglądał bardziej jak aktor niż „typowy” pseudo-proceduralny OO.
Kyoryu,
1
+1 Używam Symfony 2, DI jest na całym kodzie użytkownika, właśnie go używa.
MGP
45

Największym minusem inwersji kontroli (niezupełnie DI, ale wystarczająco blisko) jest to, że ma tendencję do usuwania jednego punktu, aby spojrzeć na przegląd algorytmu. Zasadniczo dzieje się tak, gdy oddzielisz kod - umiejętność patrzenia w jednym miejscu jest artefaktem ścisłego łączenia.

Kyoryu
źródło
2
Ale z pewnością ten „minus” wynika z natury problemu, który rozwiązujemy, odsprzęgnięcie go, abyśmy mogli łatwo zmienić implementację, oznacza, że ​​nie ma jednego miejsca, na które można spojrzeć, i jakie znaczenie ma możliwość spojrzenia na to? Jedyny przypadek, jaki mogę wymyślić, to debugowanie, a środowisko debugowania powinno mieć możliwość wkroczenia do implementacji.
vickirk
3
Dlatego słowo „minus” było w cudzysłowie :) Luźne sprzężenie i silne enkapsulacja wykluczają „jedno miejsce, aby zobaczyć wszystko”, z definicji. Zobacz moje komentarze do odpowiedzi Havarda S, jeśli masz wrażenie, że jestem przeciw DI / IoC.
Kyoryu
1
Zgadzam się (głosowałem nawet za tobą). Właśnie miałem na myśli.
vickirk
1
@vickirk: Myślę, że minusem jest to, że człowiekowi trudno jest pojąć. Oddzielenie rzeczy zwiększa złożoność w tym sensie, że ludziom trudniej jest zrozumieć i potrzeba więcej czasu na ich pełne zrozumienie.
Bjarke Freund-Hansen
2
Wręcz przeciwnie, jedną z zalet DI jest to, że możesz spojrzeć na konstruktora pojedynczej klasy i od razu dowiedzieć się, od których klas zależy (tj. Jakie klasy muszą wykonać swoją pracę). Jest to o wiele trudniejsze w przypadku innego kodu, ponieważ kod może tworzyć klasy chcąc nie chcąc.
Contango,
42

Nie wydaje mi się, żeby taka lista istniała, jednak spróbuj przeczytać te artykuły:

Gabriel Ščerbák
źródło
7
Jestem ciekawy, kiedy ktoś pyta o wnętrze, dlaczego odpowiedzi w duchu „nie ma”, są uprzywilejowane, a te, które zawierają pewne informacje związane z pytaniem, nie?
Gabriel Ščerbák
12
-1 ponieważ nie szukam kolekcji linków, szukam rzeczywistych odpowiedzi: co powiesz na podsumowanie punktów każdego artykułu? Potem głosowałbym zamiast tego. Zauważ, że same artykuły są dość interesujące, choć wydaje się, że pierwsze dwa w rzeczywistości obalają negatywy DI?
Epaga,
4
Zastanawiam się, czy najpopularniejsza odpowiedź również została doceniona, ponieważ tak naprawdę w ogóle nie odpowiada na pytanie imho :) Jasne, wiem, o co pytałeś i na co odpowiedziałem, nie pytam o opinie, jestem tylko zdezorientowany, dlaczego moja odpowiedź nie pomaga, podczas gdy najwyraźniej mówi się, że wadą DI jest to, że jest zbyt fajnie, bardzo pomaga.
Gabriel Ščerbák
41

Używam Guice (frameworku Java DI) intensywnie przez ostatnie 6 miesięcy. Chociaż ogólnie uważam, że jest świetny (szczególnie z perspektywy testowania), istnieją pewne wady. W szczególności:

  • Kod może być trudniejszy do zrozumienia. Wstrzykiwanie zależności można wykorzystać na bardzo ... kreatywne ... sposoby. Na przykład właśnie natknąłem się na kod, który użył niestandardowej adnotacji do wstrzyknięcia określonych strumieni IOStream (np .: @ Server1Stream, @ Server2Stream). Chociaż to działa, i przyznam, że ma pewną elegancję, sprawia, że ​​zrozumienie zastrzyków Guice jest warunkiem koniecznym do zrozumienia kodu.
  • Wyższa krzywa uczenia się podczas uczenia się projektu. Jest to związane z punktem 1. Aby zrozumieć, jak działa projekt wykorzystujący wstrzykiwanie zależności, należy zrozumieć zarówno wzorzec wstrzykiwania zależności, jak i konkretną strukturę. Kiedy zacząłem swoją obecną pracę, spędziłem sporo zdezorientowanych godzin, rozmyślając o tym, co Guice robi za kulisami.
  • Konstruktory stają się duże. Chociaż można to w dużej mierze rozwiązać za pomocą domyślnego konstruktora lub fabryki.
  • Błędy można zaciemnić. Moim najnowszym przykładem było zderzenie dwóch nazw flag. Guice przełknął cicho błąd i jedna z moich flag nie została zainicjowana.
  • Błędy są przekazywane do czasu wykonywania. Jeśli niepoprawnie skonfigurujesz moduł Guice (odwołanie cykliczne, złe powiązanie, ...) większość błędów nie zostanie wykryta podczas kompilacji. Zamiast tego błędy są ujawniane podczas faktycznego uruchamiania programu.

Teraz, kiedy narzekałem. Powiem, że będę nadal (chętnie) używał Guice w moim obecnym projekcie i najprawdopodobniej w moim następnym. Wstrzykiwanie zależności jest doskonałym i niewiarygodnie silnym wzorem. Ale to z pewnością może być mylące i prawie na pewno poświęcisz trochę czasu na przeklinanie dowolnej wybranej struktury wstrzykiwania zależności.

Zgadzam się również z innymi plakatami, że zastrzyk zależności może być nadużywany.

Andy Peck
źródło
14
Nie rozumiem, dlaczego ludzie nie robią większego interesu w kwestii „Błędy są spychane do czasu wykonywania”, co jest dla mnie przełomem, statyczne pisanie i błędy czasu kompilacji są największym darem dla programistów, nie rzucałbym je na wszystko
Richard Tingle,
2
@RichardTingle, IMO podczas uruchamiania aplikacji Moduły DI zostaną najpierw zainicjowane, więc każda błędna konfiguracja w module będzie widoczna natychmiast po uruchomieniu aplikacji, a nie po kilku dniach lub czasie. Możliwe jest również stopniowe ładowanie modułów, ale jeśli trzymamy się ducha DI, ograniczając ładowanie modułu przed zainicjowaniem logiki aplikacji, możemy z powodzeniem odizolować złe powiązania na początku aplikacji. Ale jeśli skonfigurujemy go jako anty-wzorzec lokalizatora usług, te złe powiązania z pewnością będą niespodzianką.
k4vin
@RichardTingle Rozumiem, że nigdy nie będzie podobny do siatki bezpieczeństwa zapewnianej przez kompilator, ale DI jako narzędzie używane poprawnie, jak wskazano w pudełku, wówczas błędy w czasie wykonywania są ograniczone do inicjalizacji aplikacji. Następnie możemy spojrzeć na inicjalizację aplikacji jako rodzaj fazy kompilacji modułów DI. Z mojego doświadczenia wynika, że ​​jeśli aplikacja zostanie uruchomiona, nie będzie w niej złych powiązań ani niepoprawnych odniesień. PS - Używam NInject z C #
k4vin
@RichardTingle - Zgadzam się z tobą, ale jest to kompromis w celu uzyskania luźno sprzężonego, a zatem testowalnego kodu. I jak powiedział k4vin, podczas inicjowania wykryto brakujące zależności, a używanie interfejsów nadal pomoże w kompilacji błędów czasu.
Andrei Epure
1
Dodałbym do listy „Trudny do utrzymania kodu”. Nigdy nie wiadomo, czy można usunąć rejestrację, zanim bez niej nie przetestuje się kodu.
fernacolo
24

Kod bez żadnych DI wiąże się ze znanym ryzykiem zaplątania się w kod Spaghetti - niektóre objawy są takie, że klasy i metody są zbyt duże, robią za dużo i nie można ich łatwo zmienić, rozbić, zreformować lub przetestować.

Często używanym kodem z DI może być kod Ravioli, w którym każda mała klasa jest jak pojedyncza bryłka ravioli - robi jedną małą rzecz i przestrzegana jest zasada pojedynczej odpowiedzialności , co jest dobre. Ale patrząc na zajęcia samodzielnie, trudno jest zobaczyć, co robi system jako całość, ponieważ zależy to od tego, jak wszystkie te małe części pasują do siebie, co jest trudne do zobaczenia. Wygląda jak wielki stos małych rzeczy.

Unikając złożoności spaghetti dużych fragmentów sprzężonego kodu w dużej klasie, ryzykujesz innym rodzajem złożoności, w której istnieje wiele prostych małych klas, a interakcje między nimi są złożone.

Nie sądzę, że jest to fatalny minus - DI wciąż jest bardzo opłacalny. Pewien styl ravioli z małymi klasami, które robią tylko jedną rzecz, jest prawdopodobnie dobry. Nawet w nadmiarze nie sądzę, że jest zły jako kod spaghetti. Ale świadomość, że można to zrobić za daleko, jest pierwszym krokiem do uniknięcia tego. Skorzystaj z linków, aby omówić, jak tego uniknąć.

Anthony
źródło
1
Tak, podoba mi się ten termin „kod ravioli”; Wyrażam to z większą rozwagą, kiedy mówię o wadach DI. Mimo to nie wyobrażam sobie tworzenia prawdziwego frameworka lub aplikacji w Javie bez DI.
Howard M. Lewis Ship
13

Jeśli masz domowe rozwiązanie, konstruktor ma przed sobą zależności. A może jako parametry metody, które znowu nie są zbyt trudne do wykrycia. Chociaż zależności zarządzane przez framework, jeśli zostaną doprowadzone do ostateczności, mogą zacząć wyglądać jak magia.

Jednak zbyt wiele zależności w zbyt wielu klasach jest wyraźnym znakiem, że twoja struktura klas jest zepsuta. W ten sposób zastrzyk zależności (domowy lub zarządzany przez framework) może pomóc w wydobyciu rażących problemów projektowych, które w przeciwnym razie mogłyby zostać ukryte w ciemności.


Aby lepiej zilustrować drugą kwestię, oto fragment tego artykułu ( oryginalne źródło ), który, moim zdaniem, jest fundamentalnym problemem w budowaniu dowolnego systemu, nie tylko systemów komputerowych.

Załóżmy, że chcesz zaprojektować kampus. Musisz przekazać część projektu studentom i profesorom, w przeciwnym razie budynek Fizyki nie będzie działał dobrze dla fizyków. Żaden architekt nie wie wystarczająco dużo o tym, czego ludzie potrzebują do fizyki, aby zrobić to wszystko sami. Ale nie możesz przekazać projektu każdego pokoju jego mieszkańcom, ponieważ wtedy dostaniesz gigantyczną kupę gruzu.

Jak możesz rozdzielić odpowiedzialność za projektowanie na wszystkie poziomy dużej hierarchii, zachowując jednocześnie spójność i harmonię ogólnego projektu? Jest to problem projektowania architektonicznego, który Alexander próbuje rozwiązać, ale jest to także podstawowy problem rozwoju systemów komputerowych.

Czy DI rozwiązuje ten problem? Nie . Pomaga to jednak wyraźnie zobaczyć, czy próbujesz przekazać odpowiedzialność za zaprojektowanie każdego pokoju jego mieszkańcom.

Anurag
źródło
13

Jedną rzeczą, która sprawia, że ​​trochę się mylę z DI, jest założenie, że wszystkie wstrzyknięte obiekty są tanie w tworzeniu i nie wywołują żadnych skutków ubocznych - LUB - zależność jest używana tak często, że przewyższa wszelkie powiązane koszty tworzenia.

Może to mieć znaczenie, gdy zależność nie jest często używana w klasie konsumującej; takie jak coś IExceptionLogHandlerService. Oczywiście taka usługa jest wywoływana (miejmy nadzieję :)) rzadko w klasie - prawdopodobnie tylko w wyjątkach wymagających zalogowania; ale kanoniczny wzorzec konstruktora-wtrysku ...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

... wymaga zapewnienia „rzeczywistej” instancji tej usługi, przeklętej kosztów / skutków ubocznych wymaganych do jej uzyskania. Nie żeby tak się stało, ale co by było, gdyby skonstruowanie tego wystąpienia zależności obejmowało trafienie usługi / bazy danych, wyszukiwanie plików konfiguracyjnych lub zablokowanie zasobu do momentu usunięcia? Jeśli ta usługa została skonstruowana zgodnie z potrzebami, zlokalizowana w serwisie lub wygenerowana fabrycznie (wszystkie mają własne problemy), wówczas koszty budowy byłyby podejmowane tylko w razie potrzeby.

Obecnie jest ogólnie przyjętą zasadą projektowania oprogramowania, że ​​konstruowanie obiektu jest tanie i nie powoduje skutków ubocznych. I chociaż jest to miłe pojęcie, nie zawsze tak jest. Jednak zastosowanie typowego wtrysku konstruktora zasadniczo tego wymaga. To znaczy, kiedy tworzysz implementację zależności, musisz zaprojektować ją z myślą o DI. Być może spowodowałbyś, że budowa obiektów byłaby bardziej kosztowna, aby uzyskać korzyści gdzie indziej, ale jeśli ta implementacja zostanie wprowadzona, prawdopodobnie zmusi cię do ponownego rozważenia tego projektu.

Nawiasem mówiąc, niektóre techniki mogą złagodzić ten dokładny problem, umożliwiając leniwe ładowanie wstrzykiwanych zależności, np. Dostarczając klasie Lazy<IService>instancję jako zależność. Spowodowałoby to zmianę konstruktorów obiektów zależnych i uczynienie jeszcze bardziej świadomymi szczegółów implementacji, takich jak koszty budowy obiektów, co również prawdopodobnie nie jest pożądane.

ckittel
źródło
1
Rozumiem, co mówisz, ale nie sądzę, aby można było powiedzieć „przy tworzeniu implementacji zależności należy zaprojektować ją z myślą o DI” - myślę, że dokładniejszym stwierdzeniem byłoby „DI działa najlepiej, gdy jest stosowany z klasami, które są wdrażane zgodnie z najlepszymi praktykami dotyczącymi kosztów tworzenia instancji i braku efektów ubocznych lub trybów awarii podczas budowy ”. W najgorszym przypadku zawsze można wstrzyknąć leniwą implementację proxy, która odkłada przydział rzeczywistego obiektu do pierwszego użycia.
Jolly Roger
1
Każdy nowoczesny kontener IoC pozwoli ci określić czas życia obiektu dla określonego abstrakcyjnego typu / interfejsu (zawsze unikalny, singleton, zasięg http itp.). Możesz również podać fabryczną metodę / delegata, aby utworzyć ją leniwie za pomocą Func <T> lub Lazy <T>.
Dmitry S.
Spot on. Tworzenie zależności zależnie niepotrzebnie kosztuje pamięć. Zwykle używam „leniwego ładowania” z modułami pobierającymi, które wywołują klasę tylko wtedy, gdy są wywoływane. Możesz podać domyślny konstruktor bez parametrów, a także kolejnych konstruktorów, które akceptują zależne instancje. W pierwszym przypadku klasa implementująca IErrorLogger jest tworzona na przykład tylko this.errorLogger.WriteError(ex)wtedy, gdy wystąpi błąd w instrukcji try / catch.
John Bonfardeci
12

Złudzenie, że oddzieliłeś swój kod po prostu przez wdrożenie wstrzykiwania zależności, a NIE ODCZYTYWANIE go. Myślę, że to najbardziej niebezpieczna rzecz w DI.

Joey Guerra
źródło
11

To bardziej nitpick. Ale jedną z wad wstrzykiwania zależności jest to, że narzędzia programistyczne nieco utrudniają rozumowanie kodu i poruszanie się po nim.

W szczególności, jeśli klikniesz z wciśniętym klawiszem Control / Command + wywołanie metody w kodzie, nastąpi przejście do deklaracji metody w interfejsie zamiast konkretnej implementacji.

Jest to naprawdę wada luźno sprzężonego kodu (kod zaprojektowany przez interfejs) i ma zastosowanie, nawet jeśli nie używasz wstrzykiwania zależności (tj. Nawet jeśli po prostu używasz fabryk). Ale pojawienie się zastrzyku zależności naprawdę zachęciło luźno powiązany kod do mas, więc pomyślałem, że o tym wspomnę.

Ponadto zalety luźno powiązanego kodu znacznie przewyższają to, dlatego nazywam to nitpick. Chociaż pracowałem wystarczająco długo, aby wiedzieć, że jest to rodzaj odpychania, które możesz uzyskać, jeśli spróbujesz wprowadzić zastrzyk zależności.

W rzeczywistości zaryzykuję zgadnięcie, że za każdym „minusem” zastrzyku zależności można znaleźć wiele zalet, które znacznie go przewyższają.

Jack Leow
źródło
5
Ctrl-shift-B z resharper zabiera cię do implementacji
adrianm
1
Ale z drugiej strony nie mielibyśmy (w idealnym świecie) co najmniej 2 implementacji wszystkiego, tj. Co najmniej jednej rzeczywistej implementacji i
próbnej
1
Jeśli przytrzymasz Ctrl i najedziesz myszką na kod wywołania metody w Eclipse, wyświetli się menu, aby otworzyć Deklarację lub Implementację.
Sam Hasler,
Jeśli jesteś przy definicji interfejsu, podświetl nazwę metody i naciśnij Ctrl + T, aby wyświetlić hierarchię typów dla tej metody - zobaczysz każdego implementatora tej metody w drzewie hierarchii typów.
Qualidafial
10

Konstruktor oparte wtrysku zależność (bez pomocy magicznych „ramy”) jest czysty i korzystny sposób na kod struktura oo. W najlepszych bazach kodów, jakie widziałem, przez lata spędzone z innymi byłymi kolegami Martina Fowlera, zacząłem zauważać, że większość dobrych klas napisanych w ten sposób kończy się na jednej doSomethingmetodzie.

Główną wadą jest to, że kiedy zdasz sobie sprawę, że jest to tylko niechlujny, długotrwały sposób OO do pisania zamknięć jako klas, aby uzyskać korzyści z programowania funkcjonalnego, twoja motywacja do pisania kodu OO może szybko zniknąć.

sanityinc
źródło
1
Problem z konstruktora oparte jest, że zawsze trzeba dodać kolejne argumenty konstruktora ...
Miguel Ping
1
Ha ha. Jeśli zrobisz to za dużo, wkrótce zobaczysz, że twój kod jest kiepski i poprawisz go. Poza tym Ctrl-F6 nie jest taki trudny.
time4tea
I oczywiście jest też odwrotnie: jest to po prostu nieprzyzwoity funkcjonalny sposób pisania klas jako zamknięć, aby uzyskać korzyści z programowania OO
CurtainDog
Wiele lat temu zbudowałem system obiektowy za pomocą zamknięć w Perlu, więc rozumiem, co mówisz, ale kapsułkowanie danych nie jest wyjątkową zaletą programowania OO, więc nie jest jasne, jakie są te korzystne cechy OO, które byłyby porównywalne kludgy i długowieczny, aby uzyskać w funkcjonalnym języku.
sanityinc
9

Uważam, że wstrzyknięcie konstruktora może prowadzić do dużych brzydkich konstruktorów (i używam go w całej mojej bazie kodu - być może moje obiekty są zbyt szczegółowe?). Czasami też po wstrzyknięciu konstruktora mam okropne zależności kołowe (chociaż jest to bardzo rzadkie), więc może się okazać, że trzeba mieć jakiś gotowy cykl życia stanu z kilkoma rundami wstrzykiwania zależności w bardziej złożonym systemie.

Jednak wolę wstrzykiwanie konstruktora niż wstrzykiwanie setera, ponieważ po zbudowaniu mojego obiektu bez wątpienia wiem, w jakim jest stanie, czy znajduje się w środowisku testów jednostkowych, czy jest załadowany do jakiegoś pojemnika IOC. Co w pewnym sensie mówi, że to, co czuję, jest główną wadą wtrysku setera.

(na marginesie uważam, że cały temat jest dość „religijny”, ale twój przebieg będzie się różnić w zależności od poziomu technicznej gorliwości w zespole deweloperów!)

James B.
źródło
8
Jeśli masz duże brzydkie konstruktory, być może twoje klasy są zbyt duże i po prostu musisz mieć wiele zależności?
Robert
4
Jest to z pewnością możliwość, którą chętnie podejmę! ... Niestety nie mam zespołu, z którym mógłbym dokonywać recenzji. skrzypce grają cicho w tle, a następnie ekran zmienia kolor na czarny ...
James B
1
Jak Mark Seeman zasmucił powyżej „To może być niebezpieczne dla twojej kariery” ...
Robert
Nie miałem pojęcia, że ​​narracje w tle mogą być tak wyraziste: P
Anurag
@Robert Próbuję znaleźć cytat, gdzie powyżej to powiedział?
liang
8

Jeśli używasz DI bez kontenera IOC, największym minusem jest to, że szybko widzisz, ile zależności ma Twój kod i jak bardzo wszystko jest ściśle powiązane. („Ale myślałem, że to dobry projekt!”) Naturalnym postępem jest przejście do kontenera MKOl, którego nauka i wdrożenie może zająć trochę czasu (nie tak źle, jak krzywa uczenia się WPF, ale nie jest za darmo zarówno). Ostatnim minusem jest to, że niektórzy programiści zaczną pisać rzetelne testy jednostkowe dobroci i zajmie im to trochę czasu. Deweloperzy, którzy wcześniej potrafili coś wykręcić w ciągu pół dnia, nagle spędzą dwa dni, próbując wymyślić, jak kpić ze wszystkich swoich zależności.

Podobnie jak odpowiedź Marka Seemanna, najważniejsze jest to, że spędzasz czas, stając się lepszym programistą, zamiast hakować razem fragmenty kodu i wyrzucać go na zewnątrz / do produkcji. Który wolałby twój biznes? Tylko Ty możesz na to odpowiedzieć.

Mike Post
źródło
Słuszna uwaga. To jest niska jakość / szybka dostawa w porównaniu do dobrej jakości / wolna dostawa. Minusem? DI nie jest magią, oczekiwanie, że będzie to wada.
liang
5

DI to technika lub wzorzec, niezwiązany z żadnym szkieletem. Możesz połączyć swoje zależności ręcznie. DI pomaga ci z SR (pojedyncza odpowiedzialność) i SoC (oddzielenie problemów). DI prowadzi do lepszego projektu. Z mojego punktu widzenia i doświadczenia nie ma wad . Podobnie jak w przypadku każdego innego wzoru, możesz go źle pomylić lub niewłaściwie go użyć (ale w przypadku DI jest to dość trudne).

Jeśli wprowadzisz DI jako zasadę do starszej aplikacji, używając frameworka - największym błędem, jaki możesz zrobić, jest niewłaściwe użycie go jako Service-Locater. Sama platforma DI + jest świetna i właśnie poprawiła wszystko, gdzie ją widziałem! Z organizacyjnego punktu widzenia występują wspólne problemy z każdym nowym procesem, techniką, wzorem ...:

  • Musisz wyszkolić swój zespół
  • Musisz zmienić aplikację (w tym ryzyko)

Ogólnie rzecz biorąc, musisz zainwestować czas i pieniądze , poza tym nie ma żadnych wad, naprawdę!

Robert
źródło
3

Czytelność kodu Nie będziesz w stanie łatwo obliczyć przepływu kodu, ponieważ zależności są ukryte w plikach XML.

Rahul
źródło
9
Nie musisz używać plików konfiguracyjnych XML do wstrzykiwania zależności - wybierz kontener DI, który obsługuje konfigurację w kodzie.
Håvard S
8
Wstrzykiwanie zależności niekoniecznie oznacza pliki XML. Wygląda na to, że nadal może tak być w przypadku Javy, ale w .NET zrezygnowaliśmy z tego połączenia lata temu.
Mark Seemann,
6
Lub w ogóle nie używaj pojemnika DI.
Jesse Millikan,
jeśli wiesz, że kod używa DI, możesz łatwo założyć, kto ustawia zależności.
Bozho
W Javie Google Guice nie potrzebuje na przykład plików XML.
Epaga
2

Dwie rzeczy:

  • Wymagają dodatkowego wsparcia narzędziowego w celu sprawdzenia poprawności konfiguracji.

Na przykład IntelliJ (wersja komercyjna) obsługuje sprawdzanie poprawności konfiguracji Spring i będzie oznaczać błędy, takie jak naruszenia typu w konfiguracji. Bez tego rodzaju obsługi narzędzi nie można sprawdzić, czy konfiguracja jest poprawna przed uruchomieniem testów.

Jest to jeden z powodów, dla których wzór „placka” (znany społeczności Scala) jest dobrym pomysłem: okablowanie między komponentami można sprawdzić za pomocą sprawdzania typu. Nie masz tej korzyści z adnotacjami lub XML.

  • To sprawia, że ​​globalna statyczna analiza programów jest bardzo trudna.

Ramy takie jak Spring czy Guice utrudniają ustalenie statyczne, jak będzie wyglądał wykres obiektu utworzony przez kontener. Chociaż tworzą wykres obiektowy podczas uruchamiania kontenera, nie zapewniają użytecznych interfejsów API opisujących wykres obiektowy, który / miałby / zostać utworzony.

Martin Ellis
źródło
1
To jest absolutny byk. Wstrzykiwanie zależności jest koncepcją, nie wymaga szkieletu szkieletowego. Wszystko czego potrzebujesz to nowy. Porzuć wiosnę i całe to badziewie, wtedy twoje narzędzia będą działały dobrze, a ponadto będziesz mógł znacznie lepiej refaktoryzować kod.
time4tea
Prawda, dobra uwaga. Powinienem był bardziej jasno powiedzieć, że mówiłem o problemach ze strukturami DI, a nie o schemacie. Możliwe, że przeoczyłem wyjaśnienie pytania, kiedy odpowiedziałem (zakładając, że było ono w tym czasie).
Martin Ellis
Ach W takim przypadku chętnie wycofuję swoje rozdrażnienie. przeprosiny.
time4tea
2

Wygląda na to, że rzekome korzyści płynące ze stosowania języka o typie statycznym znacznie się zmniejszają, gdy ciągle stosuje się techniki do obejścia pisania statycznego. Jeden z dużych sklepów Java, w którym właśnie przeprowadziłem wywiad, mapował ich zależności kompilacji za pomocą statycznej analizy kodu ... który musiał przeanalizować wszystkie pliki Spring, aby był skuteczny.

Chris D.
źródło
1

Może wydłużyć czas uruchamiania aplikacji, ponieważ kontener IoC powinien poprawnie rozwiązywać zależności, a czasem wymaga kilku iteracji.

rzymski
źródło
3
Aby podać liczbę, kontener DI powinien rozwiązać wiele tysięcy zależności na sekundę. ( codinginstinct.com/2008/05/… ) Kontenery DI umożliwiają odroczenie tworzenia instancji. Wydajność nie powinna (nawet w dużych aplikacjach) stanowić problemu. Lub przynajmniej problem z wydajnością powinien być możliwy do rozwiązania i nie powinien być powodem do podjęcia decyzji przeciwko IoC i jego ramom.
Robert