Szukam implementacji wstrzykiwania zależności w stosunkowo dużej aplikacji, ale nie mam w tym doświadczenia. Studiowałem koncepcję i kilka implementacji IoC oraz dostępnych wtryskiwaczy zależności, takich jak Unity i Ninject. Jednak jedna rzecz mnie umyka. Jak powinienem zorganizować tworzenie instancji w mojej aplikacji?
Myślę o tym, że mogę stworzyć kilka konkretnych fabryk, które będą zawierały logikę tworzenia obiektów dla kilku konkretnych typów klas. Zasadniczo klasa statyczna z metodą wywołującą metodę Ninject Get () instancji statycznego jądra w tej klasie.
Czy będzie to poprawne podejście do implementacji wstrzykiwania zależności w mojej aplikacji, czy też powinienem wdrożyć ją zgodnie z inną zasadą?
c#
.net
dependency-injection
inversion-of-control
ninject
użytkownik3223738
źródło
źródło
Odpowiedzi:
Nie myśl jeszcze o narzędziu, którego będziesz używać. Możesz wykonać DI bez kontenera IoC.
Pierwszy punkt: Mark Seemann ma bardzo dobrą książkę o DI w .Net
Po drugie: skład główny. Upewnij się, że cała konfiguracja została wykonana w punkcie wejścia projektu. Reszta kodu powinna wiedzieć o zastrzykach, a nie o każdym używanym narzędziu.
Po trzecie: wstrzyknięcie konstruktora jest najbardziej prawdopodobną drogą (istnieją przypadki, w których nie chcesz tego, ale nie tak wiele).
Po czwarte: rozważ użycie fabryk lambda i innych podobnych funkcji, aby uniknąć tworzenia niepotrzebnych interfejsów / klas wyłącznie w celu wstrzyknięcia.
źródło
Twoje pytanie składa się z dwóch części - jak prawidłowo wdrożyć DI i jak refaktoryzować dużą aplikację, aby móc korzystać z DI.
Na pierwszą część odpowiada dobrze @Miyamoto Akira (szczególnie zalecenie przeczytania książki Marka Seemanna o „wstrzykiwaniu zależności w .net”. Blog Marks jest również dobrym darmowym zasobem.
Druga część jest znacznie bardziej skomplikowana.
Dobrym pierwszym krokiem byłoby po prostu przeniesienie całej instancji do konstruktorów klas - nie wstrzykiwanie zależności, tylko upewnienie się, że wywołujesz tylko
new
konstruktor.Spowoduje to wyróżnienie wszystkich naruszeń SRP, które popełniłeś, dzięki czemu możesz zacząć dzielić klasę na mniejszych współpracowników.
Kolejnym zagadnieniem, które znajdziesz, będą klasy oparte na parametrach środowiska wykonawczego podczas budowy. Zwykle można to naprawić, tworząc proste fabryki, często
Func<param,type>
inicjując je w konstruktorze i wywołując je metodami.Następnym krokiem byłoby stworzenie interfejsów dla twoich zależności i dodanie drugiego konstruktora do twoich klas, który oprócz tych interfejsów. Twój bezparametrowy konstruktor odświeżyłby konkretne instancje i przekazał je nowemu konstruktorowi. Jest to powszechnie nazywane „B * stard Injection” lub „Poor mans DI”.
To da ci możliwość przeprowadzenia niektórych testów jednostkowych, a jeśli to był główny cel refaktora, może to być miejsce, w którym się zatrzymasz. Nowy kod zostanie napisany z zastrzykiem konstruktora, ale stary kod może nadal działać tak, jak napisano, ale nadal może być testowany.
Możesz oczywiście pójść dalej. Jeśli zamierzasz użyć kontenera IOC, następnym krokiem może być zastąpienie wszystkich bezpośrednich wywołań
new
w bezparametrowych konstruktorach wywołaniami statycznymi do kontenera IOC, zasadniczo (ab) przy użyciu go jako lokalizatora usług.Spowoduje to wyświetlenie większej liczby przypadków parametrów konstruktora środowiska wykonawczego, z którymi należy się uporać.
Po wykonaniu tej czynności możesz rozpocząć usuwanie konstruktorów bez parametrów i przefiltrować do czystego DI.
Ostatecznie będzie to dużo pracy, więc upewnij się, że zdecydujesz, dlaczego chcesz to zrobić, i nadaj priorytet tym częściom kodu, które najbardziej skorzystają na refaktorze
źródło
Najpierw chcę wspomnieć, że znacznie utrudniasz sobie to, dokonując refaktoryzacji istniejącego projektu, a nie rozpoczynając nowy.
Powiedziałeś, że jest to duża aplikacja, więc na początek wybierz mały komponent. Najlepiej komponent „liść-węzeł”, który nie jest używany przez nic innego. Nie wiem, jaki jest stan testów automatycznych w tej aplikacji, ale przełamiesz wszystkie testy jednostkowe tego komponentu. Więc bądź na to przygotowany. Krok 0 polega na napisaniu testów integracji dla komponentu, który będziesz modyfikować, jeśli jeszcze nie istnieją. W ostateczności (brak infrastruktury testowej; brak wpisu do jej napisania), wymyśl szereg ręcznych testów, które możesz wykonać, aby sprawdzić, czy ten komponent działa.
Najprostszym sposobem określenia celu dla refaktora DI jest usunięcie wszystkich wystąpień „nowego” operatora z tego komponentu. Zasadniczo można je podzielić na dwie kategorie:
Niezmienna zmienna składowa: są to zmienne ustawiane raz (zazwyczaj w konstruktorze) i nieprzypisywane na czas życia obiektu. W tym celu można wstrzyknąć instancję obiektu do konstruktora. Na ogół nie jesteś odpowiedzialny za pozbywanie się tych obiektów (nie chcę powiedzieć, że nigdy tutaj, ale tak naprawdę nie powinieneś ponosić tej odpowiedzialności).
Wariant element członkowski zmienna / zmienna metody: są to zmienne, które będą zbierać śmieci w pewnym momencie podczas życia obiektu. W tym przypadku będziesz chciał wstrzyknąć fabrykę do swojej klasy, aby zapewnić te wystąpienia. Odpowiadasz za usuwanie obiektów utworzonych przez fabrykę.
Twój kontener IoC (wygląda na to, że tak wygląda) weźmie odpowiedzialność za tworzenie instancji tych obiektów i implementację interfejsów fabrycznych. Cokolwiek korzysta ze zmodyfikowanego komponentu, musi wiedzieć o kontenerze IoC, aby mógł odzyskać komponent.
Po wykonaniu powyższych czynności będziesz mógł czerpać korzyści, jakie masz nadzieję uzyskać z DI w wybranym komponencie. Teraz byłby dobry moment, aby dodać / naprawić te testy jednostkowe. Jeśli istniały testy jednostkowe, musisz podjąć decyzję, czy chcesz je połączyć razem, wstrzykując prawdziwe obiekty, czy pisać nowe testy jednostkowe przy użyciu próbnych testów.
Po prostu powtórz powyższe czynności dla każdego komponentu aplikacji, przesuwając odniesienie do kontenera IoC w górę, dopóki tylko główny nie będzie musiał o tym wiedzieć.
źródło
Prawidłowym podejściem jest użycie wstrzykiwania konstruktora, jeśli go używasz
to kończy się na lokalizatorze usług, a nie na wstrzykiwaniu zależności.
źródło
Mówisz, że chcesz go użyć, ale nie mów dlaczego.
DI to nic innego jak zapewnienie mechanizmu generowania konkrecji z interfejsów.
To samo w sobie pochodzi z DIP . Jeśli Twój kod jest już napisany w tym stylu i masz jedno miejsce, w którym generowane są konkrecje, DI nie wnosi nic więcej na imprezę. Dodanie tutaj kodu frameworku DI po prostu rozproszy i zaciemni bazę kodu.
Zakładając, że chcesz go używać, zazwyczaj konfigurujesz fabrykę / konstruktora / kontenera (lub cokolwiek innego) na początku aplikacji, aby był wyraźnie widoczny.
Uwaga: bardzo łatwo jest rzucić własny, jeśli chcesz, zamiast zaangażować się w Ninject / StructureMap lub cokolwiek innego. Jeśli jednak masz rozsądną rotację personelu, może nasmarować koła, aby użyć uznanej ramy lub przynajmniej napisać ją w tym stylu, aby nie była zbytnio krzywą uczenia się.
źródło
Właściwie „właściwym” sposobem jest wcale NIE używanie fabryki, chyba że absolutnie nie ma innego wyboru (jak w testach jednostkowych i niektórych próbach - dla kodu produkcyjnego NIE używasz fabryki)! Jest to w rzeczywistości anty-wzór i należy go za wszelką cenę unikać. Istotą kontenera DI jest umożliwienie gadżetowi wykonania pracy za Ciebie.
Jak stwierdzono powyżej w poprzednim poście, chcesz, aby Twój gadżet IoC przejął odpowiedzialność za tworzenie różnych zależnych obiektów w Twojej aplikacji. Oznacza to, że gadżet DI może samodzielnie tworzyć różne instancje i zarządzać nimi. To jest cały punkt za DI - twoje obiekty NIGDY nie powinny wiedzieć, jak tworzyć i / lub zarządzać obiektami, na których polegają. W przeciwnym razie zrywa luźne połączenie.
Konwersja istniejącej aplikacji na wszystkie DI jest ogromnym krokiem, ale pomijając oczywiste trudności w zrobieniu tego, będziesz również chciał (tylko żeby twoje życie było trochę łatwiejsze) zbadać narzędzie DI, które automatycznie wykona większość twoich powiązań (rdzeń czegoś takiego jak Ninject to
"kernel.Bind<someInterface>().To<someConcreteClass>()"
wywołania, które wykonujesz w celu dopasowania deklaracji interfejsu do tych konkretnych klas, których chcesz użyć do implementacji tych interfejsów. To te wywołania „Bind”, które pozwalają Twojemu gadżetowi DI przechwytywać wywołania konstruktora i zapewniać niezbędne instancje obiektów zależnych. Typowym konstruktorem (pokazany tutaj pseudo kod) dla niektórych klas może być:Zauważ, że nigdzie w tym kodzie nie było żadnego kodu, który utworzyłby / zarządzał / wydał ani wystąpienie SomeConcreteClassA lub SomeOtherConcreteClassB. W rzeczywistości żadna konkretna klasa nie została nawet przywołana. Więc ... gdzie stała się magia?
W części początkowej aplikacji miały miejsce następujące zdarzenia (znowu jest to pseudo kod, ale jest bardzo zbliżony do rzeczywistej (Ninject) rzeczy ...):
Ta odrobina kodu mówi gadżetowi Ninject, aby szukał konstruktorów, skanował je, szukał instancji interfejsów, które zostały skonfigurowane do obsługi (to są wywołania „Bind”), a następnie tworzył i zastępował instancję konkretnej klasy, gdziekolwiek instancja jest przywoływana.
Istnieje ładne narzędzie, które bardzo dobrze uzupełnia Ninject o nazwie Ninject.Extensions.Conventions (kolejny pakiet NuGet), które wykona większość tej pracy za Ciebie. Nie odrywając się od doskonałych doświadczeń edukacyjnych, przez które przechodzisz, gdy sam to budujesz, ale aby zacząć, może to być narzędzie do zbadania.
Jeśli pamięć służy, Unity (formalnie Microsoft jest teraz projektem Open Source) ma wywołanie metody lub dwie, które robią to samo, inne narzędzia mają podobne pomocniki.
Niezależnie od tego, którą ścieżkę wybierzesz, zdecydowanie przeczytaj książkę Marka Seemanna, aby uzyskać większą część szkolenia DI, jednak należy zauważyć, że nawet „wielcy” świata inżynierii oprogramowania (jak Mark) mogą popełniać rażące błędy - Mark zapomniał o wszystkim Ninject w swojej książce, więc oto kolejny zasób napisany tylko dla Ninject. Mam go i jest to dobra lektura: Mastering Ninject for Dependency Injection
źródło
Nie ma „właściwej drogi”, ale należy przestrzegać kilku prostych zasad:
To wszystko. Z pewnością są to zasady, a nie prawa, ale jeśli będziesz ich przestrzegać, możesz być pewien, że robisz DI (popraw mnie, jeśli się mylę).
Jak więc tworzyć obiekty w czasie wykonywania bez „nowego” i bez znajomości kontenera DI?
W przypadku NInject istnieje rozszerzenie fabryczne, które zapewnia tworzenie fabryk. Jasne, utworzone fabryki nadal mają wewnętrzną aktualizację jądra, ale nie jest dostępna z twojej aplikacji.
źródło