Niedawno pracowałem nad projektem Python, w którym intensywnie wykonaliśmy wstrzykiwanie zależności (ponieważ musimy, aby aplikacja była testowalna), ale nie użyliśmy żadnego frameworka. Czasami ręczne ułożenie wszystkich zależności było trochę uciążliwe, ale ogólnie działało świetnie.
Gdy obiekt musiał zostać utworzony w wielu miejscach, mieliśmy po prostu funkcję (czasem klasową metodę tego obiektu), aby utworzyć instancję produkcyjną. Ta funkcja była wywoływana za każdym razem, gdy potrzebowaliśmy tego obiektu.
W testach zrobiliśmy to samo: jeśli wiele razy musieliśmy stworzyć „wersję testową” obiektu (tj. Rzeczywistą instancję popartą próbnymi obiektami), mieliśmy funkcję do stworzenia tej „wersji testowej” klasy, do wykorzystania w testach.
Jak powiedziałem, ogólnie działało dobrze.
Wchodzę teraz w nowy projekt Java i decydujemy, czy użyć frameworka DI, czy wykonać DI ręcznie (jak w poprzednim projekcie).
Wyjaśnij, jak będzie wyglądała praca ze środowiskiem DI oraz w jaki sposób będzie on lepszy lub gorszy niż ręczne wstrzykiwanie zależności „waniliowej”.
Edycja: Chciałbym również dodać do pytania: czy widziałeś jakikolwiek projekt „profesjonalny”, który wykonuje DI ręcznie, bez ram?
źródło
Odpowiedzi:
Kluczem do pojemników DI jest abstrakcja . Kontenery DI stanowią streszczenie tego problemu dla Ciebie. Jak można się domyślić, ma zauważalny wpływ na kod, często tłumaczony na mniejszą liczbę konstruktorów, seterów, fabryk i konstruktorów. W rezultacie sprawiają, że Twój kod jest luźniej sprzężony (z powodu IoC ), czystszy i prostszy. Chociaż nie bez kosztów.
Tła
Wiele lat temu taka abstrakcja wymagała od nas pisania różnych plików konfiguracyjnych ( programowanie deklaratywne ). Fantastycznie było widzieć, że liczba LOC znacznie się zmniejsza, ale chociaż LOCS spadły, liczba plików konfiguracyjnych nieznacznie wzrosła. W przypadku średnich i małych projektów wcale nie było problemu, ale w przypadku większych projektów rozproszenie „zestawu kodu” o 50% między kodem a deskryptorami XML stało się uciążliwe ... Niemniej jednak głównym problemem było sam paradygmat. Było dość mało elastyczne, ponieważ deskryptory pozostawiły niewiele miejsca na modyfikacje.
Paradygmat stopniowo przechodził w stronę konwencji nad konfiguracją , która wymienia pliki konfiguracyjne na adnotacje lub atrybuty. Utrzymuje nasz kod w czystości i prostocie, zapewniając jednocześnie elastyczność, której XML nie mógł.
Prawdopodobnie jest to najważniejsza różnica w stosunku do dotychczasowego sposobu pracy. Mniej kodu i więcej magii.
Z drugiej strony ...
Konwencja dotycząca konfiguracji sprawia, że abstrakcja jest tak ciężka, że ledwo wiesz, jak to działa. Czasami wydaje się magia i pojawia się jeszcze jedna wada. Kiedy już działa, wielu programistów nie dba o to, jak to działa.
Jest to utrudnienie dla tych, którzy nauczyli się DI z pojemnikami DI. Nie w pełni rozumieją znaczenie wzorców projektowych, dobrych praktyk i zasad wyodrębnionych przez kontener. Zapytałem programistów, czy są zaznajomieni z DI i jego zaletami i odpowiedzieli: - Tak, użyłem Spring IoC -. (Co do diabła to znaczy?!?)
Z każdą z tych „wad” można się zgodzić lub nie. Moim skromnym zdaniem trzeba wiedzieć, co dzieje się w twoim projekcie. W przeciwnym razie nie rozumiemy tego w pełni. Warto również wiedzieć, do czego służy DI i mieć pojęcie, jak ją wdrożyć.
Na plus...
Jedno słowo wydajność . Ramy pozwalają skupić się na tym, co naprawdę ważne. Biznes aplikacji.
Pomimo skomentowanych niedociągnięć, dla wielu z nas (których praca jest uwarunkowana terminami i kosztami) narzędzia te są nieocenionym zasobem. Moim zdaniem powinien to być nasz główny argument za wdrożeniem kontenerów DI. Produktywność .
Testowanie
Niezależnie od tego, czy wdrożymy czystą DI, czy kontenery, testowanie nie będzie stanowić problemu. Wręcz przeciwnie. Te same pojemniki często dostarczają nam próbnych testów jednostkowych po wyjęciu z pudełka. Przez lata korzystałem z moich testów jako termometrów. Mówią mi, czy w dużym stopniu polegałem na obiektach kontenerowych. Mówię to, ponieważ te kontenery mogą inicjować prywatne atrybuty bez ustawiaczy i konstruktorów. Mogą wstrzykiwać komponenty prawie w dowolnym miejscu!
Brzmi kusząco, prawda? Bądź ostrożny! Nie wpadnij w pułapkę !!
Propozycje
Jeśli zdecydujesz się na wdrożenie kontenerów, zdecydowanie zalecam stosowanie dobrych praktyk. Kontynuuj wdrażanie konstruktorów, ustawiaczy i interfejsów. Wymuszaj użycie frameworka / kontenera . Ułatwi to możliwe migracje do innego frameworka lub jego usunięcie. Może znacznie zmniejszyć zależność od kontenera.
W odniesieniu do tych praktyk:
Kiedy stosować pojemniki DI
Moja odpowiedź będzie stronnicza z własnego doświadczenia, które jest głównie na korzyść kontenerów DI (jak skomentowałem, jestem bardzo skoncentrowany na wydajności, więc nigdy nie potrzebowałem czystej DI. Wręcz przeciwnie).
Myślę, że znajdziesz interesujący artykuł Marka Seemana na ten temat, który również odpowiada na pytanie, jak wdrożyć czystą DI .
Na koniec, jeśli mówimy o Javie, rozważyłbym najpierw JSR330 - Dependency Injection .
Zreasumowanie
Korzyści :
Wady :
Różnice z czystym DI :
źródło
Z mojego doświadczenia wynika, że struktura wstrzykiwania zależności prowadzi do wielu problemów. Niektóre problemy będą zależeć od struktury i narzędzi. Mogą istnieć praktyki łagodzące problemy. Ale to są problemy, które dotknąłem.
Obiekty nieglobalne
W niektórych przypadkach chcesz wstrzyknąć obiekt globalny: obiekt, który będzie miał tylko jedną instancję w uruchomionej aplikacji. Może to być
MarkdownToHtmlRendererer
, ADatabaseConnectionPool
, adres dla serwera API, itp dla tych przypadków, ramy wtrysku zależność działa w bardzo prosty sposób, wystarczy dodać potrzebne zależność do konstruktora i masz go.Ale co z obiektami, które nie są globalne? Co jeśli potrzebujesz użytkownika do bieżącego żądania? Co jeśli potrzebujesz aktualnie aktywnej transakcji bazy danych? Co jeśli potrzebujesz elementu HTML odpowiadającego div, który zawiera formularz, w którym jest pole tekstowe, które właśnie uruchomiło zdarzenie?
Rozwiązaniem, które widziałem stosowane przez frameworki DI, są hierarchiczne wtryskiwacze. Ale to czyni model wtrysku bardziej skomplikowanym. Trudniej jest ustalić, co zostanie wstrzyknięte z jakiej warstwy hierarchii. Trudno powiedzieć, czy dany obiekt będzie istniał dla aplikacji, użytkownika, żądania itp. Nie można już tylko dodawać swoich zależności i sprawić, by działały.
Biblioteki
Często będziesz chciał mieć bibliotekę kodu wielokrotnego użytku. Obejmuje to również instrukcje dotyczące konstruowania obiektów z tego kodu. Jeśli używasz struktury wstrzykiwania zależności, zwykle będzie to obejmować reguły wiązania. Jeśli chcesz korzystać z biblioteki, dodajesz do niej wiążące reguły.
Mogą jednak wystąpić różne problemy. Możesz skończyć z tą samą klasą związaną z różnymi implementacjami. Biblioteka może oczekiwać pewnych powiązań. Biblioteka może zastąpić niektóre domyślne wiązania, powodując dziwne działania kodu. Twój kod może zastąpić niektóre domyślne wiązania, powodując dziwne rzeczy w kodzie biblioteki.
Ukryta złożoność
Podczas ręcznego pisania fabryk masz poczucie, w jaki sposób pasują do siebie zależności aplikacji. Kiedy używasz struktury wstrzykiwania zależności, nie widzisz tego. Zamiast tego obiekty mają coraz więcej zależności, aż prędzej czy później wszystko zależy od prawie wszystkiego.
Obsługa IDE
Twoje IDE prawdopodobnie nie zrozumie struktury wstrzykiwania zależności. Dzięki ręcznym fabrykom możesz znaleźć wszystkie obiekty wywołujące konstruktora, aby dowiedzieć się o wszystkich miejscach, w których obiekt może zostać zbudowany. Ale nie znajdzie połączeń generowanych przez środowisko DI.
Wniosek
Co otrzymujemy ze struktury wstrzykiwania zależności? Unikamy prostackiej płyty potrzebnej do tworzenia obiektów. Jestem za eliminacją prostych pomysłów. Jednak utrzymanie prostej płyty kotłowej jest łatwe. Jeśli zamierzamy wymienić płytę kotła, powinniśmy nalegać na zastąpienie jej czymś łatwiejszym. Z powodu różnych zagadnień, które omówiłem powyżej, nie sądzę, aby ramy (przynajmniej takie, jakie stoją) pasowały do rachunku.
źródło
Czy wymagane jest wstrzykiwanie zależności Java = framework?
Nie.
Co kupuje środowisko DI?
Konstrukcja obiektu odbywa się w języku innym niż Java.
Jeśli metody fabryczne są wystarczająco dobre dla twoich potrzeb, możesz wykonać DI w java całkowicie za darmo w 100% autentyczne pojo java. Jeśli twoje potrzeby wykraczają poza to, masz inne opcje.
Wzory projektowe Gang of Four pozwalają osiągnąć wiele wspaniałych rzeczy, ale zawsze były słabe, jeśli chodzi o wzory kreacyjne. Sama Java ma tutaj pewne słabości. Frameworki DI naprawdę pomagają w tej słabości twórczej bardziej niż w przypadku rzeczywistych zastrzyków. Wiesz już, jak to zrobić bez nich. Ile dobrego ci zrobią, zależy od tego, ile pomocy potrzebujesz przy budowie obiektów.
Istnieje wiele wzorców twórczych, które pojawiły się po Gangu Czterech. Konstruktor Josh Bloch to wspaniały hack wokół braku nazwanych parametrów Java. Poza tym są konstruktory iDSL, które oferują dużą elastyczność. Ale każdy z nich reprezentuje znaczną pracę i płytę kotłową.
Ramy mogą cię uratować przed niektórymi z tego nudy. Nie są pozbawione wad. Jeśli nie jesteś ostrożny, możesz stać się zależny od frameworka. Spróbuj zmienić ramy po użyciu jednego i przekonaj się sam. Nawet jeśli w jakiś sposób utrzymujesz ogólny kod XML lub adnotacje, możesz w końcu obsługiwać zasadniczo dwa języki, o których musisz wspominać obok siebie, gdy reklamujesz zadania programistyczne.
Zanim zaczniesz się zbytnio zakochać w potędze frameworków DI, poświęć trochę czasu i zbadaj inne frameworki, które dadzą ci możliwości, które w innym przypadku mogą ci się wydawać potrzebne. Wiele z nich wykracza poza proste DI . Zastanów się: Lombok , AspectJ i slf4J . Każda nakłada się na niektóre frameworki DI, ale każda z nich działa dobrze samodzielnie.
Jeśli testowanie jest naprawdę siłą napędową tego, proszę spojrzeć na: jUnit (naprawdę xUnit) i Mockito (jakikolwiek mock naprawdę), zanim zaczniesz myśleć, że środowisko DI powinno przejąć twoje życie.
Możesz robić, co chcesz, ale do poważnej pracy wolę dobrze wypełnioną skrzynkę narzędziową niż szwajcarski scyzoryk. Szkoda, że nie było łatwiej narysować tych linii, ale niektórzy ciężko pracowali, aby je zatrzeć. Nie ma w tym nic złego, ale może to utrudnić wybór.
źródło
Tworzenie klas i przypisywanie im zależności jest częścią procesu modelowania, zabawnymi częściami. To jest powód, dla którego większość programistów lubi programować.
Decyzja o tym, która implementacja zostanie użyta i jak skonstruowane są klasy, należy w jakiś sposób zaimplementować i jest częścią konfiguracji aplikacji.
Ręczne pisanie fabryk to żmudne dzieło. Frameworki DI ułatwiają automatyczne rozwiązywanie zależności, które mogą zostać rozwiązane, i wymagają konfiguracji dla tych, które mogą nie.
Korzystanie z nowoczesnych IDE umożliwia nawigację między metodami i ich zastosowaniami. Ręczne pisanie fabryk jest całkiem dobre, ponieważ możesz po prostu wyróżnić konstruktora klasy, pokazać jego zastosowania, a pokaże Ci wszystkie miejsca, w których i jak budowana jest dana klasa.
Ale nawet w środowisku DI możesz mieć centralne miejsce, takie jak konfiguracja konstrukcji wykresów obiektowych (od tej pory OGCC), a jeśli klasa zależy od niejednoznacznych zależności, możesz po prostu zajrzeć do OGCC i zobaczyć, jak budowana jest konkretna klasa tam.
Możesz sprzeciwić się, że osobiście uważasz, że możliwość korzystania z określonego konstruktora to świetny plus. Powiedziałbym, że to, co wychodzi ci lepiej, jest nie tylko subiektywne, ale przede wszystkim pochodzi z osobistego doświadczenia.
Gdybyś zawsze pracował nad projektami opartymi na frameworkach DI, tak naprawdę wiedziałbyś tylko to, a kiedy chciałeś zobaczyć konfigurację klasy, zawsze patrzyłbyś na OGCC i nawet nie próbowałbyś zobaczyć, jak używane są konstruktory , ponieważ wiedziałbyś, że wszystkie zależności i tak są podłączone automatycznie.
Oczywiście frameworki DI nie mogą być używane do wszystkiego i co jakiś czas będziesz musiał napisać metodę fabryczną. Bardzo częstym przypadkiem jest konstruowanie zależności podczas iteracji nad kolekcją, gdzie każda iteracja dostarcza inną flagę, która powinna wytworzyć inną implementację inaczej wspólnego interfejsu.
Ostatecznie najprawdopodobniej wszystko sprowadzi się do twoich preferencji i tego, co chcesz poświęcić (czas pisania fabryk lub przejrzystość).
Osobiste doświadczenie (ostrzeżenie: subiektywne)
Mówiąc jako programista PHP, chociaż podoba mi się pomysł na frameworki DI, użyłem ich tylko raz. Wtedy zespół, z którym pracowałem, miał bardzo szybko wdrożyć aplikację i nie mogliśmy stracić czasu na pisanie fabryk. Więc po prostu wybraliśmy framework DI, skonfigurowaliśmy go i jakoś zadziałał .
W przypadku większości projektów lubię ręcznie pisać fabryki, ponieważ nie podoba mi się czarna magia zachodząca w ramach DI. Byłem odpowiedzialny za modelowanie klasy i jej zależności. To ja decydowałem, dlaczego projekt wygląda tak, jak wygląda. Będę więc tym, który poprawnie konstruuje klasę (nawet jeśli klasa przyjmuje twarde zależności, takie jak konkretne klasy zamiast interfejsów).
źródło
Osobiście nie używam pojemników DI i nie lubię ich. Mam na to jeszcze kilka powodów.
Zwykle jest to zależność statyczna. Jest to więc tylko lokalizator usług ze wszystkimi jego wadami. Następnie, ze względu na charakter zależności statycznych, są one ukryte. Nie musisz sobie z nimi radzić, oni już tam są i jest to niebezpieczne, ponieważ przestajesz je kontrolować.
To łamie zasady OOP , takie jak spójność i David West „s Lego cegły obiektu metafory.
Tak więc budowanie całego obiektu jak najbliżej punktu wejścia programu jest dobrym rozwiązaniem.
źródło
Jak już zauważyłeś: w zależności od używanego języka istnieją różne punkty widzenia, różni się, jak radzić sobie z podobnymi problemami.
W odniesieniu do wstrzykiwania zależności sprowadza się to do tego: wstrzykiwanie zależności jest, jak to się mówi, niczym więcej niż umieszczeniem zależności, których jeden obiekt potrzebuje w tym obiekcie - w przeciwieństwie do drugiej strony: pozwalanie, aby sam obiekt inicjował instancje obiektów, od których zależy. Oddzielasz tworzenie i zużycie obiektu, aby uzyskać większą elastyczność.
Jeśli twój projekt jest dobrze rozplanowany, zwykle masz kilka klas z wieloma zależnościami, dlatego zestawianie zależności - ręcznie lub za pomocą szkieletu - powinno być względnie łatwe, a ilość planszy do napisania testów powinna być łatwa do zarządzania.
To powiedziawszy, nie ma potrzeby tworzenia ram DI.
Ale dlaczego mimo wszystko chcesz używać frameworka?
I) Unikaj kodu płyty kotłowej
Jeśli twój projekt nabiera rozmiarów, w którym ciągle odczuwasz ból związany z pisaniem fabryk (i instatacji), powinieneś użyć struktury DI
II) Deklaracyjne podejście do programowania
Kiedy Java programowała kiedyś za pomocą XML , teraz jest: posypywanie bajki adnotacjami i magia się dzieje .
Ale poważnie: widzę wyraźną zaletę tego. Wyraźnie komunikujesz zamiar powiedzenia, co jest potrzebne zamiast tego, gdzie to znaleźć i jak to zbudować.
tl; dr
Frameworki DI nie poprawiają magicznie bazy kodu, ale pomagają sprawić, że dobrze wyglądający kod wygląda naprawdę ostro . Użyj go, kiedy czujesz, że go potrzebujesz. W pewnym momencie projektu zaoszczędzisz czas i zapobiegniesz mdłościom.
źródło
Tak. Prawdopodobnie powinieneś przeczytać książkę Marka Seemanna zatytułowaną „Dependency Injection”. Przedstawia kilka dobrych praktyk. W zależności od tego, co powiedziałeś, możesz skorzystać. Powinieneś dążyć do posiadania jednego katalogu głównego kompozycji lub jednego katalogu głównego kompozycji na jednostkę pracy (np. Żądanie sieciowe). Podoba mi się jego sposób myślenia, że każdy obiekt, który tworzysz, jest uważany za zależność (podobnie jak każdy parametr metody / konstruktora), więc powinieneś naprawdę uważać na to, gdzie i jak tworzysz obiekty. Ramy pomogą ci wyjaśnić te pojęcia. Jeśli czytasz książkę Marka, znajdziesz więcej niż odpowiedź na swoje pytanie.
źródło
Tak, podczas pracy w Javie polecam framework DI. Jest kilka powodów:
Java ma kilka bardzo dobrych pakietów DI, które oferują automatyczne wstrzykiwanie zależności, a także zwykle są dostarczane z dodatkowymi funkcjami, które trudniej jest napisać ręcznie niż proste DI (np. Zależności zakresowe, które umożliwiają automatyczne połączenie między zakresami poprzez generowanie kodu w celu sprawdzenia, jaki zakres powinien być użyj d, aby znaleźć poprawną wersję zależności dla tego zakresu - na przykład obiekty o zasięgu aplikacji mogą odnosić się do obiektów o zasięgu na żądanie).
Adnotacje Java są niezwykle przydatne i zapewniają doskonały sposób konfigurowania zależności
Konstrukcja obiektu Java jest zbyt wyczerpująca w porównaniu do tego, do czego jesteś przyzwyczajony w Pythonie; użycie tu frameworka oszczędza więcej pracy niż w dynamicznym języku.
Frameworki Java java mogą robić użyteczne rzeczy, takie jak automatyczne generowanie serwerów proxy i dekoratorów, które działają więcej w Javie niż w języku dynamicznym.
źródło
Jeśli twój projekt jest wystarczająco mały i nie masz dużego doświadczenia z DI-Framework, odradzam korzystanie z niego i zrobienie tego ręcznie.
Powód: Kiedyś pracowałem nad projektem, który wykorzystywał dwie biblioteki, które miały bezpośrednie zależności od różnych struktur. oba miały kod specyficzny dla frameworka, taki jak di-give-me-an-instance-of-XYZ. O ile wiem, możesz mieć tylko jedną aktywną implementację di-framework na raz.
z takim kodem możesz wyrządzić więcej szkody niż korzyści.
Jeśli masz więcej doświadczenia, prawdopodobnie pozbyłbyś się diamontażu za pomocą interfejsu (fabryka lub servicelocator), więc ten problem już nie będzie istniał, a di di framewor przyniesie więcej korzyści tej szkodzie.
źródło