Praktyki kontenerowe polegające na wstrzykiwaniu zależności / IoC podczas pisania frameworków

18

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:

  1. 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).

  2. 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ć.

  3. Łą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?

Dave Hillier
źródło
1
Jakie złe praktyki zachęcają? Jeśli chodzi o prywatne lekcje, nie powinieneś mieć ich zbyt wielu, jeśli masz dobrą enkapsulację; po pierwsze, są znacznie trudniejsze do przetestowania.
Aaronaught
Err, 1 i 2 to złe praktyki. Duże konstruktory i nie używające enkapsulacji do ustanowienia minimalnego interfejsu? Powiedziałem też: prywatne i wewnętrzne (używaj wewnętrznych elementów widocznych do testowania). Nigdy nie korzystałem z frameworka, który zmusiłby mnie do używania określonego kontenera IoC.
Dave Hillier
2
Czy to możliwe, że oznaką potrzeby posiadania wielu parametrów dla konstruktorów klas jest sam zapach kodu, a DIP czyni to bardziej oczywistym?
dreza
3
Używanie określonego kontenera IoC w ramach frameworka zasadniczo nie jest dobrą praktyką. Stosowanie wstrzykiwania zależności jest dobrą praktyką. Czy zdajesz sobie sprawę z różnicy?
Aaronaught
1
@Aaronaught (powrót z martwych). Użycie „zastrzyku zależności jest dobrą praktyką” jest fałszywe. Wstrzykiwanie zależności jest narzędziem, które powinno być używane tylko w razie potrzeby. Ciągłe używanie Dependency Injection jest złą praktyką.
Dave Hillier

Odpowiedzi:

27

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 DateTimedanych, które są danymi; zamiast tego wstrzykujesz abstrakcję przez zegar systemowy (zwykle nazywam mój ISystemClock, 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, a staticwyją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.

Aaronaught
źródło
Mam parę pytań. W przypadku biblioteki dystrybuowanej w NuGet nie mogę polegać na systemie IoC, jak to robi aplikacja korzystająca z biblioteki, a nie sama biblioteka. W wielu przypadkach użytkownik biblioteki tak naprawdę nie dba o jej wewnętrzne zależności. Czy istnieje problem z posiadaniem domyślnego konstruktora inicjującego wewnętrzne zależności i dłuższym konstruktorem do testowania jednostkowego i / lub niestandardowych implementacji? Mówisz też, żeby unikać „nowego”. Czy dotyczy to rzeczy takich jak StringBuilder, DateTime i TimeSpan?
Etienne Charland