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.
Odpowiedzi:
Kilka punktów:
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ń.
źródło
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ć ...
Mam zależność, którą chcę wyodrębnić i wstrzyknąć - tekst „Hello World”. Wystarczająco łatwe...
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.
źródło
Oto moja pierwsza reakcja: w zasadzie te same wady dowolnego wzoru.
źródło
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.
źródło
Nie wydaje mi się, żeby taka lista istniała, jednak spróbuj przeczytać te artykuły:
DI może ukryć kod (jeśli nie pracujesz z dobrym IDE)
Nadużycie IoC może prowadzić do złego kodu według wuja Boba.
Musisz uważać na nadmierną inżynierię i tworzenie niepotrzebnej wszechstronności.
źródło
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:
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.
źródło
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ąć.
źródło
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.
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.
źródło
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 ...... 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.źródło
this.errorLogger.WriteError(ex)
wtedy, gdy wystąpi błąd w instrukcji try / catch.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.
źródło
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ą.
źródło
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
doSomething
metodzie.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ąć.
źródło
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!)
źródło
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ć.
źródło
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 ...:
Ogólnie rzecz biorąc, musisz zainwestować czas i pieniądze , poza tym nie ma żadnych wad, naprawdę!
źródło
Czytelność kodu Nie będziesz w stanie łatwo obliczyć przepływu kodu, ponieważ zależności są ukryte w plikach XML.
źródło
Dwie rzeczy:
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.
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.
źródło
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.
źródło
Może wydłużyć czas uruchamiania aplikacji, ponieważ kontener IoC powinien poprawnie rozwiązywać zależności, a czasem wymaga kilku iteracji.
źródło