Użyłem różnych kontenerów IoC (Castle.Windsor, Autofac, MEF itp.) Dla .Net w wielu projektach. Zauważyłem, że są one często nadużywane i zachęcają do wielu złych praktyk.
Czy istnieją jakieś ustalone praktyki dotyczące korzystania z kontenerów IoC, szczególnie w przypadku zapewniania platformy / platformy? Moim celem jako twórcy frameworku jest uczynienie kodu tak prostym i tak łatwym w użyciu, jak to możliwe. Wolę napisać jeden wiersz kodu, aby zbudować obiekt, niż dziesięć, a nawet dwa.
Na przykład kilka kodów pachnie, które zauważyłem i nie mam dobrych sugestii, aby:
Duża liczba parametrów (> 5) dla konstruktorów. Tworzenie usług bywa skomplikowane; wszystkie zależności są wstrzykiwane przez konstruktor - pomimo tego, że komponenty rzadko są opcjonalne (może z wyjątkiem testowania).
Brak zajęć prywatnych i wewnętrznych; ten może być specyficznym ograniczeniem używania C # i Silverlight, ale interesuje mnie sposób jego rozwiązania. Trudno powiedzieć, czym jest interfejs frameworka, jeśli wszystkie klasy są publiczne; umożliwia mi dostęp do części prywatnych, których prawdopodobnie nie powinienem dotykać.
Łączenie cyklu życia obiektu z kontenerem IoC. Często trudno jest ręcznie konstruować zależności wymagane do tworzenia obiektów. Cykl życia obiektu jest zbyt często zarządzany przez platformę IoC. Widziałem projekty, w których większość klas jest zarejestrowana jako Singletons. Dostajesz brak wyraźnej kontroli, a także jesteś zmuszony zarządzać wewnętrznymi elementami (odnosi się to do powyższego punktu, wszystkie klasy są publiczne i musisz je wstrzyknąć).
Na przykład .NET Framework ma wiele metod statycznych. takie jak DateTime.UtcNow. Wiele razy widziałem to zapakowane i wstrzyknięte jako parametr konstrukcyjny.
W zależności od konkretnej implementacji mój kod jest trudny do przetestowania. Wstrzyknięcie zależności sprawia, że mój kod jest trudny w użyciu - szczególnie jeśli klasa ma wiele parametrów.
Jak zapewnić zarówno interfejs testowalny, jak i łatwy w użyciu? Jakie są najlepsze praktyki?
Odpowiedzi:
Jedynym uzasadnionym anty-wzorcem wstrzykiwania zależności, o którym wiem, jest wzorzec Service Locator , który jest anty-wzorcem, gdy używana jest do niego struktura DI.
Wszystkie inne tak zwane anty-wzorce DI, o których słyszałem, tutaj lub gdzie indziej, są tylko nieco bardziej szczegółowymi przypadkami ogólnych anty-wzorów projektowania OO / oprogramowania. Na przykład:
Nadmierne wstrzyknięcie przez konstruktora stanowi naruszenie zasady pojedynczej odpowiedzialności . Zbyt wiele argumentów konstruktora wskazuje na zbyt wiele zależności; zbyt wiele zależności wskazuje, że klasa próbuje zrobić za dużo. Zwykle ten błąd koreluje z innymi zapachami kodu, takimi jak niezwykle długie lub niejednoznaczne nazwy klas („manager”). Narzędzia analizy statycznej mogą łatwo wykryć nadmierne sprzężenie aferentne / odprowadzające.
Wstrzykiwanie danych, w przeciwieństwie do zachowania, jest podtypem anty-wzorca poltergeist , przy czym geist w tym przypadku jest pojemnikiem. Jeśli klasa musi wiedzieć o bieżącej dacie i godzinie, nie wstrzykujesz
DateTime
danych, które są danymi; zamiast tego wstrzykujesz abstrakcję przez zegar systemowy (zwykle nazywam mójISystemClock
, chociaż myślę, że jest bardziej ogólny w projekcie SystemWrappers ). Jest to nie tylko poprawne dla DI; jest to absolutnie niezbędne dla testowalności, abyś mógł testować zmienne w czasie funkcje bez konieczności faktycznego oczekiwania na nie.Deklarowanie każdego cyklu życia jako Singleton jest dla mnie doskonałym przykładem programowania kultu kultowego, aw mniejszym stopniu potocznie nazywanym „ obiektowym szambo ”. Widziałem więcej przypadków nadużywania singletonów, niż pamiętam, i bardzo niewiele z nich dotyczy DI.
Innym częstym błędem są specyficzne dla implementacji typy interfejsów (o dziwnych nazwach, takie jak
IOracleRepository
), wykonywane tylko po to, aby móc zarejestrować je w kontenerze. To samo w sobie stanowi naruszenie zasady inwersji zależności (tylko dlatego, że jest to interfejs, co nie znaczy, że jest naprawdę abstrakcyjne) i często obejmuje również wzdęcie interfejsu, które narusza zasadę segregacji interfejsu .Ostatnim błędem, który zazwyczaj widzę, jest „opcjonalna zależność”, którą zrobili w NerdDinner . Innymi słowy, istnieje konstruktor, który akceptuje iniekcji zależność, ale również inny konstruktor, który wykorzystuje „default” wdrożenie. To również jest niezgodny DIP i ma tendencję do prowadzić do LSP naruszeń, jak również, jako twórców, z biegiem czasu, zacznij założenia wokół realizacji domyślny i / lub rozpocząć nowe instancje-ing się przy użyciu domyślnego konstruktora.
Jak mówi stare powiedzenie, możesz pisać FORTRAN w dowolnym języku . Dependency Injection nie jest panaceum, które uniemożliwi deweloperom przekręcane zarządzanie nimi zależności, ale nie zapobiegają wielu typowych błędów / przeciw wzorów:
...i tak dalej.
Oczywiście nie chcesz projektować struktury zależnej od konkretnej implementacji kontenera IoC , takiej jak Unity lub AutoFac. To po raz kolejny narusza DIP. Ale jeśli nawet zastanawiasz się nad zrobieniem czegoś takiego, musiałeś już popełnić kilka błędów projektowych, ponieważ Dependency Injection jest techniką zarządzania zależnościami ogólnego przeznaczenia i nie jest związana z koncepcją kontenera IoC.
Wszystko może skonstruować drzewo zależności; może to jest kontener IoC, może to test jednostkowy z wieloma próbnymi próbami, może to sterownik testowy dostarczający fałszywe dane. Twój framework nie powinien się tym przejmować, a większość frameworków, które widziałem, nie przejmuje się tym, ale nadal intensywnie wykorzystują wstrzykiwanie zależności, dzięki czemu można je łatwo zintegrować z wybranym kontenerem IoC użytkownika końcowego.
DI to nie nauka rakietowa. Po prostu starają się unikać
new
, astatic
wyjątkiem sytuacji, gdy istnieje powód, aby z nich korzystać, takie jak metoda narzędzie, które nie ma zależności zewnętrzne, lub klasie użytkowej, która nie mogła mieć żadnego celu poza ramami (współdziałanie owijarki i klucze słownika są typowe przykłady to).Wiele problemów ze strukturami IoC pojawia się, gdy programiści uczą się, jak z nich korzystać, i zamiast zmieniać sposób obsługi zależności i abstrakcji w celu dopasowania do modelu IoC, zamiast tego próbuj manipulować kontenerem IoC, aby spełnić oczekiwania ich stary styl kodowania, który często wymagałby wysokiego sprzężenia i niskiej spójności. Zły kod to zły kod, niezależnie od tego, czy korzysta on z technik DI, czy nie.
źródło