Mam proste pytanie i nawet nie jestem pewien, czy ma odpowiedź, ale spróbujmy. Piszę w C ++ i używam wstrzykiwania zależności, aby uniknąć stanu globalnego. Działa to całkiem nieźle i często nie spotykam się z nieoczekiwanymi / niezdefiniowanymi zachowaniami.
Jednak zdaję sobie sprawę, że w miarę rozwoju mojego projektu piszę dużo kodu, który uważam za bojler. Gorzej: fakt, że kodu jest więcej, niż kod rzeczywisty sprawia, że czasami trudno go zrozumieć.
Nic nie jest dobrym przykładem, więc chodźmy:
Mam klasę o nazwie TimeFactory, która tworzy obiekty Time.
Aby uzyskać więcej informacji (nie jestem pewien, czy ma to znaczenie): Obiekty czasu są dość złożone, ponieważ czas może mieć różne formaty, a konwersja między nimi nie jest ani liniowa, ani prosta. Każdy „Czas” zawiera Synchronizator do obsługi konwersji i aby upewnić się, że mają ten sam, właściwie zainicjowany synchronizator, używam TimeFactory. TimeFactory ma tylko jedną instancję i ma szerokie zastosowanie, więc kwalifikuje się do singletonu, ale ponieważ jest zmienny, nie chcę, aby był singletonem
W mojej aplikacji wiele klas musi tworzyć obiekty Time. Czasami te klasy są głęboko zagnieżdżone.
Powiedzmy, że mam klasę A, która zawiera instancje klasy B i tak dalej do klasy D. Klasa D musi tworzyć obiekty czasu.
W mojej naiwnej implementacji przekazuję TimeFactory do konstruktora klasy A, który przekazuje go do konstruktora klasy B i tak dalej, aż do klasy D.
Teraz wyobraź sobie, że mam kilka klas, takich jak TimeFactory i kilka hierarchii klas, takich jak ta powyżej: Tracę całą elastyczność i czytelność, które, jak sądzę, mogę uzyskać za pomocą wstrzykiwania zależności.
Zaczynam się zastanawiać, czy w mojej aplikacji nie ma poważnej wady projektowej ... Czy jest to zło konieczne przy użyciu zastrzyku uzależnienia?
Co myślisz ?
źródło
Odpowiedzi:
Wydaje się, że twoja
Time
klasa jest bardzo podstawowym typem danych, który powinien należeć do „ogólnej infrastruktury” twojej aplikacji. DI nie działa dobrze dla takich klas. Zastanów się, co to znaczy, gdyby klasa takastring
musiała zostać wstrzyknięta do każdej części kodu, która używa łańcuchów, a trzeba by użyćstringFactory
jedynej możliwości tworzenia nowych łańcuchów - czytelność twojego programu zmniejszyłaby się o rząd wielkość.Więc moja sugestia: nie używaj DI do ogólnych typów danych, takich jak
Time
. Napisz testy jednostkowe dlaTime
samej klasy, a kiedy to zrobisz, używaj jej wszędzie w swoim programie, tak jakstring
klasa,vector
klasa lub jakakolwiek inna klasa standardowej biblioteki lib. Użyj DI dla komponentów, które powinny być naprawdę oddzielone od siebie.źródło
Time
innymi częściami programu. Więc możesz zaakceptować również ścisłe połączenie zTimeFactory
. Jednak unikałbym posiadania pojedynczegoTimeFactory
obiektu globalnego ze stanem (na przykład informacje o lokalizacji lub coś w tym rodzaju) - co może powodować nieprzyjemne skutki uboczne dla twojego programu i bardzo utrudniać ogólne testowanie i ponowne użycie. Uczyń go bezpaństwowcem lub nie używaj go jako singletona.Time
orazTimeFactory
od stopnia „ewolucji”, którego potrzebujesz do przyszłych rozszerzeń związanych zTimeFactory
itd.Co rozumiesz przez „tracę całą elastyczność i czytelność, jak sądzę, dzięki zastosowaniu wstrzykiwania zależności” - DI nie dotyczy czytelności. Chodzi o oddzielenie zależności między obiektami.
Wygląda na to, że masz klasę A tworzącą klasę B, klasę B tworzącą klasę C i klasę C tworzącą klasę D.
To, co powinieneś mieć, to wstrzyknięcie klasy B do klasy A. Wstrzyknięcie klasy C do klasy B. Wstrzyknięcie klasy D do klasy C.
źródło
Nie jestem pewien, dlaczego nie chcesz, aby fabryka czasu stała się singlem. Jeśli jest tylko jedna instancja w całej aplikacji, jest to de facto singleton.
Biorąc to pod uwagę, dzielenie się zmiennym przedmiotem jest bardzo niebezpieczne, z wyjątkiem sytuacji, gdy jest on odpowiednio chroniony przez synchronizowane bloki, w którym to przypadku nie ma powodu, aby nie był singletonem.
Jeśli chcesz wykonać wstrzyknięcie zależności, możesz przyjrzeć się ramkom wstrzykiwania sprężynowego lub innym wstrzykiwaniu zależności, które umożliwiłyby automatyczne przypisywanie parametrów za pomocą adnotacji
źródło
Ma to być uzupełniającą odpowiedzią na doktora Browna, a także odpowiedzieć na komentarze Dinaiz bez odpowiedzi, które są nadal związane z pytaniem.
Prawdopodobnie potrzebujesz ramy do wykonywania DI. Posiadanie złożonych hierarchii niekoniecznie oznacza zły projekt, ale jeśli musisz wstrzyknąć oddolne TimeFactory (od A do D) zamiast wstrzykiwać bezpośrednio do D, prawdopodobnie jest coś złego w sposobie wykonywania Dependency Injection.
Singleton? Nie, dziękuję. Jeśli potrzebujesz tylko jednej istoty, udostępnij ją w kontekście aplikacji (użycie kontenera IoC dla DI, takiego jak Infector ++, wymaga tylko powiązania TimeFactory jako pojedynczej instancji), oto przykład (C ++ 11 przy okazji, ale tak C ++. Może przenieś do C ++ 11? Otrzymujesz aplikację Leak-Free za darmo):
Teraz zaletą kontenera IoC jest to, że nie musisz przekazywać fabryki czasu do D. Jeśli twoja klasa „D” potrzebuje fabryki czasu, po prostu ustaw fabrykę czasu jako parametr konstruktora dla klasy D.
jak widzisz, wstrzykujesz TimeFactory tylko raz. Jak używać „A”? Bardzo proste, każda klasa jest wstrzykiwana, budowana w głównej postaci lub zakładana fabrycznie.
za każdym razem, gdy utworzysz klasę A, zostanie ona automatycznie wstrzyknięta (leniwa istnienie) ze wszystkimi zależnościami do D, a D zostanie wstrzyknięta TimeFactory, więc wywołując tylko 1 metodę masz pełną hierarchię (a nawet skomplikowane hierarchie są rozwiązywane w ten sposób usuwając DUŻO kodu płyty kotłowej): Nie musisz dzwonić „nowy / usuń”, a to bardzo ważne, ponieważ możesz oddzielić logikę aplikacji od kodu kleju.
To proste, twój TimeFactory ma metodę „utwórz”, a następnie użyj innej sygnatury „utwórz (parametry)” i gotowe. Parametry bez zależności są często rozwiązywane w ten sposób. Eliminuje to również obowiązek wstrzykiwania rzeczy takich jak „ciągi” lub „liczby całkowite”, ponieważ to po prostu dodaje dodatkową płytę kotła.
Kto tworzy kogo? Kontener IoC tworzy istoty i fabryki, fabryki tworzą resztę (fabryki mogą tworzyć różne obiekty o dowolnych parametrach, więc tak naprawdę nie potrzebujesz stanu dla fabryk). Nadal możesz używać fabryk jako opakowań dla kontenera IoC: ogólnie mówiąc, wstrzykiwanie kontenera IoC jest bardzo złe i jest identyczne z używaniem lokalizatora usług. Niektóre osoby rozwiązały problem, pakując kontener IoC w fabrykę (nie jest to absolutnie konieczne, ale ma tę zaletę, że hierarchia jest rozwiązana przez kontener, a wszystkie fabryki stają się jeszcze łatwiejsze do utrzymania).
Nie nadużywaj również wstrzykiwania zależności, proste typy mogą być po prostu elementami klasy lub zmiennymi o zasięgu lokalnym. Wydaje się to oczywiste, ale widziałem ludzi, którzy wstrzykują „std :: vector” tylko dlatego, że istniała struktura DI, która na to pozwoliła. Zawsze pamiętaj o prawie Demetera: „Wstrzykuj tylko to, czego naprawdę potrzebujesz do wstrzyknięcia”
źródło
Czy twoje klasy A, B i C również muszą tworzyć instancje czasu, czy tylko klasy D? Jeśli jest to tylko klasa D, A i B nie powinny wiedzieć nic o TimeFactory. Utwórz instancję TimeFactory w klasie C i przekaż ją do klasy D. Zauważ, że „tworzenie instancji” niekoniecznie oznacza, że klasa C musi być odpowiedzialna za tworzenie instancji TimeFactory. Może odbierać DClassFactory z klasy B, a DClassFactory wie, jak utworzyć instancję Time.
Techniką, której często używam, gdy nie mam żadnego środowiska DI, jest dostarczenie dwóch konstruktorów, jednego akceptującego fabrykę i drugiego, który tworzy fabrykę domyślną. Drugi ma zwykle dostęp chroniony / pakiet i służy głównie do testów jednostkowych.
źródło
Zaimplementowałem jeszcze inny framework wstrzykiwania zależności C ++, który niedawno został zaproponowany do ulepszenia - https://github.com/krzysztof-jusiak/di - biblioteka jest pozbawiona makr (darmowa), tylko nagłówek, C ++ 03 / C ++ 11 / C ++ 14 biblioteka zapewniająca bezpieczny typ, czas kompilacji, wstrzykiwanie zależności od konstruktora bez makr.
źródło