Czy wstrzykiwanie zależności powinno odbywać się w ctor czy według metody?

16

Rozważać:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

przeciwko:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Podczas gdy wstrzykiwanie Ctor utrudnia rozszerzenie (każdy kod wywołujący ctor będzie wymagał aktualizacji po dodaniu nowych zależności), a wstrzykiwanie na poziomie metody wydaje się być bardziej enkapsulowane z zależności na poziomie klasy i nie mogę znaleźć żadnych innych argumentów za / przeciw tym podejściom .

Czy istnieje ostateczne podejście do wstrzyknięcia?

(Uwaga: szukałem informacji na ten temat i starałem się, aby pytanie było obiektywne).

StuperUser
źródło
4
Jeśli twoje klasy są spójne, wówczas wiele różnych metod używałoby tych samych pól. To powinno dać ci odpowiedź, której szukasz ...
Oded
@Oded Dobry punkt, spójność miałaby duży wpływ na decyzję. Jeśli klasa jest, powiedzmy, zbiorem metod pomocniczych, z których każda ma różne zależności (pozornie niespójne, ale logicznie podobne), a inna jest bardzo spójna, każda z nich powinna stosować najbardziej korzystne podejście. Spowodowałoby to jednak niespójność rozwiązania.
StuperUser
Istotne dla podanych przykładów: en.wikipedia.org/wiki/False_dilemma
StuperUser

Odpowiedzi:

3

To zależy, jeśli wstrzyknięty obiekt jest używany przez więcej niż jedną metodę w klasie, a wstrzyknięcie do niego każdej metody nie ma sensu, musisz rozwiązać zależności dla każdego wywołania metody, ale jeśli zależność jest używana tylko przez jedna metoda wstrzyknięta do konstruktora nie jest dobra, ale ponieważ alokujesz zasoby, których nigdy nie można użyć.

nohros
źródło
15

Po pierwsze, porozmawiajmy o: „Każdy kod wywołujący ctor będzie wymagał aktualizacji po dodaniu nowych zależności”; Dla jasności, jeśli robisz wstrzykiwanie zależności i masz jakiś kod wywołujący new () na obiekcie z zależnościami, robisz to źle .

Twój kontener DI powinien być w stanie wstrzyknąć wszystkie odpowiednie zależności, więc nie musisz się martwić o zmianę podpisu konstruktora, aby ten argument tak naprawdę się nie utrzymywał.

Jeśli chodzi o ideę wtrysku według metody vs. dla klasy, istnieją dwa główne problemy z wtryskiem według metody.

Jednym z problemów jest to, że metody twojej klasy powinny współdzielić zależności, to jeden ze sposobów, aby upewnić się, że twoje klasy są skutecznie rozdzielone, jeśli zobaczysz klasę z dużą liczbą zależności (prawdopodobnie więcej niż 4-5), to ta klasa jest głównym kandydatem za refaktoryzację na dwie klasy.

Kolejny problem polega na tym, że aby „wstrzyknąć” zależności dla poszczególnych metod, należy przekazać je do wywołania metody. Oznacza to, że będziesz musiał rozwiązać zależności przed wywołaniem metody, więc najprawdopodobniej otrzymasz kilka takich kodów:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Powiedzmy, że wywołasz tę metodę w 10 miejscach w swojej aplikacji: będziesz mieć 10 takich fragmentów. Następnie powiedz, że musisz dodać kolejną zależność do DoStuff (): będziesz musiał zmienić ten fragment kodu 10 razy (lub owinąć go metodą, w którym to przypadku po prostu replikujesz zachowanie DI ręcznie, co jest marnotrawstwem czasu).

Zatem to, co właściwie zrobiłeś, sprawiło, że twoje klasy korzystające z DI zdają sobie sprawę z własnego kontenera DI, co jest zasadniczo złym pomysłem , ponieważ bardzo szybko prowadzi do trudnego do utrzymania projektu.

Porównaj to z iniekcją konstruktora; W iniekcji konstruktora nie jesteś przywiązany do konkretnego kontenera DI i nigdy nie jesteś bezpośrednio odpowiedzialny za wypełnianie zależności swoich klas, więc utrzymanie jest dość wolne od bólu głowy.

Wydaje mi się, że próbujesz zastosować IoC do klasy zawierającej kilka niepowiązanych metod pomocniczych, podczas gdy lepiej byłoby podzielić klasę pomocniczą na kilka klas usług na podstawie użycia, a następnie użyć pomocnika do delegowania połączenia. To wciąż nie jest świetne podejście (pomocnik klasyfikowany metodami, które robią coś bardziej złożonego niż tylko radzenie sobie z argumentami, które są do nich przekazywane, są zwykle po prostu źle napisanymi klasami usług), ale przynajmniej utrzyma twój projekt trochę czystszy .

(Zauważyłem wcześniej podejście, które sugerujesz, i to był bardzo zły pomysł, którego odtąd nie powtarzałem. Okazało się, że próbowałem wydzielić klasy, które tak naprawdę nie musiały być oddzielone, i ostatecznie skończyłem z zestawem interfejsów, w których każde wywołanie metody wymagało prawie stałego wyboru innych interfejsów. Utrzymanie tego było koszmarem).

Ed James
źródło
1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Jeśli testujesz jednostkowo metodę w klasie, musisz ją utworzyć lub czy używasz DI do utworzenia kodu w trakcie testowania?
StuperUser
@StuperUser Testowanie jednostek jest nieco innym scenariuszem, mój komentarz naprawdę dotyczy tylko kodu produkcyjnego. Ponieważ zamierzasz tworzyć symulacyjne (lub pośredniczące) zależności dla swoich testów za pomocą fałszywego frameworka, z których każde ma wstępnie skonfigurowane zachowanie oraz zestaw założeń i asercji, musisz wprowadzić je ręcznie.
Ed James
1
To jest kod, o którym mówię "any code calling the ctor will need updating", kiedy mówię , że mój kod produkcyjny nie wywołuje lekarzy. Z tych powodów.
StuperUser
2
@StuperUser Wystarczająco uczciwe, ale nadal będziesz wywoływać metody z wymaganymi zależnościami, co oznacza, że ​​reszta mojej odpowiedzi nadal obowiązuje! Szczerze mówiąc, zgadzam się na problem z wstrzykiwaniem konstruktora i wymuszaniem wszystkich zależności, z perspektywy testów jednostkowych. Zwykle używam wstrzykiwania właściwości, ponieważ pozwala to na wstrzyknięcie tylko tych zależności, które będą potrzebne do testu, który, jak uważam, jest w zasadzie kolejnym zestawem niejawnych wywołań Assert.WasNotCalled bez konieczności pisania kodu! : D
Ed James
3

Istnieją różne rodzaje inwersji kontroli , a twoje pytanie proponuje tylko dwa (możesz również użyć fabryki, aby wprowadzić zależność).

Odpowiedź brzmi: zależy to od potrzeby. Czasami lepiej jest używać jednego rodzaju, a innego innego.

Osobiście lubię wtryskiwacze konstruktorów, ponieważ konstruktor służy do pełnej inicjalizacji obiektu. Późniejsze wywołanie setera powoduje, że obiekt nie jest w pełni skonstruowany.

BЈовић
źródło
3

Przegapiłeś tę, która przynajmniej dla mnie jest najbardziej elastyczna - zastrzyk nieruchomości. Używam Castle / Windsor i wszystko, co muszę zrobić, to dodać nową właściwość auto do mojej klasy i otrzymuję wstrzyknięty obiekt bez żadnych problemów i bez zerwania interfejsów.

Otávio Décio
źródło
Jestem przyzwyczajony do aplikacji internetowych i jeśli te klasy znajdują się w warstwie Domeny, wywoływanej przez interfejs użytkownika, którego zależności zostały już rozwiązane w podobny sposób jak Wstrzykiwanie właściwości. Zastanawiam się, jak radzić sobie z klasami, do których mogę przekazać wstrzyknięte obiekty.
StuperUser
Lubię też wstrzykiwanie właściwości, po prostu dlatego nadal możesz używać normalnych parametrów konstruktora z kontenerem DI, automatycznie rozróżniając zależności rozwiązane i nierozwiązane. Ponieważ jednak zastrzyk dla konstruktora i własności jest funkcjonalnie identyczny w tym scenariuszu, postanowiłem o tym nie wspominać;)
Ed James
Wstrzykiwanie właściwości wymusza modyfikowanie każdego obiektu i powoduje tworzenie instancji w niepoprawnym stanie. Dlaczego miałoby to być lepsze?
Chris Pitman,
2
@Chris W rzeczywistości, w normalnych warunkach konstruktor i wstrzyknięcie własności działają prawie identycznie, przy czym obiekt jest w pełni wstrzykiwany podczas rozdzielczości. Jedyny raz, kiedy można to uznać za „nieważne”, jest podczas testów jednostkowych, kiedy nie zamierzasz używać kontenera DI do tworzenia instancji. Zobacz mój komentarz do mojej odpowiedzi, aby dowiedzieć się, dlaczego jest to rzeczywiście korzystne. Rozumiem, że zmienność może być problemem, ale realistycznie będziesz odwoływał się do klas rozwiązanych tylko poprzez ich interfejsy, co nie ujawni zależności do edycji.
Ed James