Wstrzykiwanie zależności przez konstruktory lub metody ustawiające właściwości?


151

Refaktoryzuję klasę i dodaję do niej nową zależność. Klasa obecnie przyjmuje swoje istniejące zależności w konstruktorze. Aby zachować spójność, dodaję parametr do konstruktora.
Oczywiście jest kilka podklas plus jeszcze więcej dla testów jednostkowych, więc teraz gram w grę polegającą na zmienianiu wszystkich konstruktorów, aby pasowały, a to trwa wieki.
To sprawia, że ​​myślę, że używanie właściwości z ustawiającymi jest lepszym sposobem uzyskiwania zależności. Nie sądzę, aby wstrzykiwane zależności były częścią interfejsu do konstruowania instancji klasy. Dodajesz zależność i teraz wszyscy twoi użytkownicy (podklasy i każdy, kto tworzy Twoją instancję bezpośrednio) nagle o tym wiedzą. To jest jak przerwa w hermetyzacji.

Wydaje się, że nie jest to wzorzec z istniejącym tu kodem, więc chcę dowiedzieć się, jaki jest ogólny konsensus, zalety i wady konstruktorów w porównaniu z właściwościami. Czy lepsze jest używanie metod ustawiających właściwości?

Odpowiedzi:


126

Cóż, to zależy :-).

Jeśli klasa nie może wykonać swojej pracy bez zależności, dodaj ją do konstruktora. Klasa potrzebuje nowej zależności, więc chcesz, aby Twoja zmiana zepsuła wszystko. Ponadto utworzenie klasy, która nie jest w pełni zainicjowana („konstrukcja dwuetapowa”) jest anty-wzorcem (IMHO).

Jeśli klasa może działać bez zależności, ustawiacz jest w porządku.


11
Myślę, że w wielu przypadkach lepiej jest użyć wzorca Null Object i trzymać się wymagania referencji w konstruktorze. Pozwala to uniknąć wszelkich sprawdzeń zerowych i zwiększonej złożoności cyklomatycznej.
Mark Lindell,

3
@Mark: Słuszna uwaga. Jednak pytanie dotyczyło dodania zależności do istniejącej klasy. Zachowanie konstruktora bezargumentowego pozwala na kompatybilność wsteczną.
sleske

A co z sytuacją, w której zależność jest potrzebna do działania, ale zwykle wystarcza domyślne wstrzyknięcie tej zależności. Zatem czy ta zależność powinna być „zastępowalna” przez właściwość lub przeciążenie konstruktora?
Patrick Szalapski

@Patrick: Mówiąc „klasa nie może wykonać swojej pracy bez zależności”, miałem na myśli, że nie ma rozsądnej wartości domyślnej (np. Klasa wymaga połączenia z bazą danych). W twojej sytuacji oba będą działać. Nadal bym wybrał podejście konstruktora, ponieważ zmniejsza to złożoność (co jeśli np. Ustawiający zostanie wywołany dwukrotnie)?
sleske


21

Użytkownicy klasy powinni wiedzieć o zależnościach danej klasy. Gdybym miał klasę, która na przykład byłaby połączona z bazą danych i nie zapewniałaby możliwości wstrzyknięcia zależności warstwy trwałości, użytkownik nigdy nie wiedziałby, że połączenie z bazą danych musi być dostępne. Jeśli jednak zmienię konstruktora, informuję użytkowników, że istnieje zależność od warstwy trwałości.

Ponadto, aby uniknąć konieczności zmieniania każdego użycia starego konstruktora, po prostu zastosuj łańcuch konstruktorów jako tymczasowy pomost między starym i nowym konstruktorem.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Jednym z punktów wstrzykiwania zależności jest ujawnienie, jakie zależności ma klasa. Jeśli klasa ma zbyt wiele zależności, może być czas na przeprowadzenie refaktoryzacji: Czy każda metoda klasy używa wszystkich zależności? Jeśli nie, to dobry punkt wyjścia, aby zobaczyć, gdzie można podzielić klasę.


Oczywiście tworzenie łańcuchów konstruktorów działa tylko wtedy, gdy nowy parametr ma rozsądną wartość domyślną. Ale poza tym i tak nie możesz uniknąć
zepsucia

Zwykle jako parametru domyślnego należy użyć tego, co było używane w metodzie przed wstrzyknięciem zależności. Idealnie byłoby, gdyby dodanie nowego konstruktora byłby czystą refaktoryzacją, ponieważ zachowanie klasy nie zmieniłoby się.
Doctor Blue

1
Rozumiem twój punkt widzenia na temat zależności zarządzania zasobami, takich jak połączenia z bazami danych. Myślę, że problem w moim przypadku polega na tym, że klasa, do której dodaję zależność, ma kilka podklas. W świecie kontenerów IOC, w którym właściwość byłaby ustawiana przez kontener, użycie metody ustawiającej przynajmniej zmniejszyłoby nacisk na powielanie interfejsu konstruktora między wszystkimi podklasami.
Niall Connaughton

17

Oczywiście założenie konstruktora oznacza, że ​​możesz zweryfikować wszystko na raz. Jeśli przypiszesz rzeczy do pól tylko do odczytu, masz pewne gwarancje dotyczące zależności obiektu od samego początku konstrukcji.

Dodawanie nowych zależności jest prawdziwym problemem, ale przynajmniej w ten sposób kompilator narzeka, dopóki nie będzie poprawny. Myślę, że to dobra rzecz.


Plus za to. W dodatku to
znacznie

11

Jeśli masz dużą liczbę opcjonalnych zależności (co już jest zapachem), prawdopodobnie najlepszym rozwiązaniem jest wstrzyknięcie setera. Wstrzyknięcie konstruktora lepiej jednak ujawnia twoje zależności.


11

Ogólnie preferowanym podejściem jest użycie iniekcji konstruktora tak często, jak to możliwe.

Wstrzyknięcie konstruktora dokładnie określa, jakie zależności są wymagane, aby obiekt działał poprawnie - nie ma nic bardziej irytującego niż tworzenie nowego obiektu i jego awaria podczas wywoływania metody, ponieważ nie jest ustawiona pewna zależność. Obiekt zwracany przez konstruktora powinien być w stanie roboczym.

Spróbuj mieć tylko jednego konstruktora, zachowuje prosty projekt i pozwala uniknąć niejednoznaczności (jeśli nie dla ludzi, dla kontenera DI).

Możesz użyć iniekcji właściwości, gdy masz to, co Mark Seemann nazywa lokalną wartością domyślną w swojej książce „Dependency Injection in .NET”: zależność jest opcjonalna, ponieważ możesz zapewnić dobrze działającą implementację, ale chcesz pozwolić wywołującemu na określenie innej, jeśli potrzebne.

(Poprzednia odpowiedź poniżej)


Myślę, że wtryski konstruktorów są lepsze, jeśli wtryski są obowiązkowe. Jeśli to dodaje zbyt wielu konstruktorów, rozważ użycie fabryk zamiast konstruktorów.

Nastrzykiwanie setera jest przyjemne, jeśli wtrysk jest opcjonalny lub jeśli chcesz go zmienić w połowie. Generalnie nie lubię seterów, ale to kwestia gustu.


Twierdzę, że zwykle zmiana wtrysku w połowie jest złym stylem (ponieważ dodajesz stan ukryty do swojego obiektu). Ale żadna reguła bez wyjątku oczywiście ...
sleske

Tak, dlatego powiedziałem, że za bardzo nie lubię setera ... Podoba mi się podejście konstruktora, bo wtedy nie da się go zmienić.
Philippe

„Jeśli to dodaje zbyt wielu konstruktorów, rozważ użycie fabryk zamiast konstruktorów”. Zasadniczo odraczasz wszelkie wyjątki w czasie wykonywania, a nawet możesz popełnić błąd i utknąć w implementacji lokalizatora usług.
MeTitus

@Marco to moja poprzednia odpowiedź i masz rację. Jeśli konstruktorów jest wielu, to argumentowałbym, że klasa robi zbyt wiele rzeczy :-) Lub rozważmy fabrykę abstrakcyjną.
Philippe

7

To w dużej mierze kwestia osobistego gustu. Osobiście wolę wstrzykiwanie setera, ponieważ uważam, że zapewnia on większą elastyczność w sposobie, w jaki można zastępować implementacje w czasie wykonywania. Ponadto konstruktory z dużą ilością argumentów nie są moim zdaniem czyste, a argumenty podane w konstruktorze powinny być ograniczone do argumentów nieopcjonalnych.

Dopóki interfejs klas (API) jasno określa, czego potrzebuje do wykonania swojego zadania, jesteś dobry.


proszę określić, dlaczego głosujesz w dół?
nkr1pt

3
Tak, konstruktorzy z wieloma argumentami są źli. Dlatego refaktoryzujesz klasy z wieloma parametrami konstruktora :-).
sleske

10
@ nkr1pt: Większość ludzi (włącznie ze mną) zgadza się, że wstrzyknięcie metody ustawiającej jest złe, jeśli pozwala na utworzenie klasy, która zawiedzie w czasie wykonywania, jeśli wstrzyknięcie nie zostanie wykonane. Myślę, że w związku z tym ktoś sprzeciwił się twierdzeniu, że to osobisty gust.
sleske

7

Osobiście wolę "wzorzec" wyodrębniania i przesłonięcia zamiast wstrzykiwania zależności w konstruktorze, głównie z powodu przedstawionego w pytaniu. Możesz ustawić właściwości jako, virtuala następnie zastąpić implementację w pochodnej testowalnej klasie.


1
Myślę, że oficjalna nazwa wzoru to „Metoda szablonu”.
davidjnelson

6

Preferuję iniekcję konstruktora, ponieważ pomaga „wymusić” wymagania dotyczące zależności klasy. Jeśli jest w c'tor, konsument musi ustawić obiekty, aby aplikacja mogła się skompilować. Jeśli używasz settera, mogą nie wiedzieć, że mają problem do czasu wykonania - iw zależności od obiektu, może to być późny czas wykonywania.

Nadal od czasu do czasu używam settera, gdy wstrzyknięty obiekt może wymagać dużo pracy, na przykład inicjalizacji.


6

Myślę o wtrysku konstruktora, bo wydaje się to najbardziej logiczne. To tak, jakby powiedzieć, że moja klasa wymaga tych zależności, aby wykonywać swoją pracę. Jeśli jest to zależność opcjonalna, właściwości wydają się rozsądne.

Używam również wtrysku właściwości do ustawiania rzeczy, do których kontener nie ma odniesień, takich jak widok ASP.NET na prezenterze utworzonym przy użyciu kontenera.

Nie sądzę, żeby to zrywało hermetyzację. Wewnętrzne działania powinny pozostać wewnętrzne, a zależności powinny dotyczyć innego problemu.


Dziękuję za odpowiedź. Z pewnością wydaje się, że konstruktor jest popularną odpowiedzią. Myślę jednak, że w jakiś sposób przerywa to hermetyzację. Wstrzyknięcie przed zależnością, klasa zadeklaruje i utworzy instancję wszystkich konkretnych typów, których potrzebuje do wykonania swojej pracy. Dzięki DI podklasy (i wszelkie ręczne instancje) wiedzą teraz, jakich narzędzi używa klasa bazowa. Dodajesz nową zależność i teraz musisz łączyć instancję ze wszystkich podklas, nawet jeśli same nie muszą używać tej zależności.
Niall Connaughton

Właśnie napisałem ładną, długą odpowiedź i straciłem ją z powodu wyjątku na tej stronie! :( Podsumowując, klasa podstawowa jest zwykle używana do ponownego użycia logiki. Ta logika może dość łatwo przejść do podklasy ... więc możesz pomyśleć o klasie podstawowej i podklasie = jeden problem, który jest zależny od wielu obiektów zewnętrznych, wykonuj różne prace. Fakt, że masz zależności, nie oznacza, że ​​musisz ujawniać wszystko, co wcześniej utrzymywałbyś w
tajemnicy

3

Jedną z opcji, którą warto rozważyć, jest tworzenie złożonych zależności wielokrotnych z prostych pojedynczych zależności. Oznacza to, że zdefiniuj dodatkowe klasy dla zależności złożonych. To sprawia, że ​​jest trochę łatwiejsze wstrzyknięcie konstruktora WRT - mniej parametrów na wywołanie - przy jednoczesnym utrzymaniu rzeczy, która musi dostarczyć wszystkie zależności, aby utworzyć instancję.

Oczywiście ma to największy sens, jeśli istnieje jakieś logiczne grupowanie zależności, więc związek jest czymś więcej niż dowolną agregacją, a najbardziej sensowne jest, jeśli istnieje wiele zależności dla jednej zależności złożonej - ale parametr „wzorzec” bloku parametrów ma istnieją od dawna, a większość z tych, które widziałem, była dość arbitralna.

Osobiście jestem jednak bardziej fanem używania metod / ustawiania właściwości do określania zależności, opcji itp. Nazwy wywołań pomagają opisać, co się dzieje. Dobrym pomysłem jest jednak podanie przykładowych fragmentów opisujących to, jak to skonfigurować, i upewnić się, że klasa zależna wykonuje wystarczającą liczbę sprawdzeń błędów. Do konfiguracji warto użyć modelu skończonego stanu.


3

Niedawno natknąłem się na sytuację, w której miałem wiele zależności w klasie, ale tylko jedna z zależności musiała się zmieniać w każdej implementacji. Ponieważ zależności dostępu do danych i rejestrowania błędów prawdopodobnie zostałyby zmienione tylko w celach testowych, dodałem opcjonalne parametry dla tych zależności i zapewniłem domyślne implementacje tych zależności w moim kodzie konstruktora. W ten sposób klasa zachowuje swoje domyślne zachowanie, chyba że zostanie nadpisane przez konsumenta klasy.

Używanie parametrów opcjonalnych można osiągnąć tylko w platformach, które je obsługują, takich jak .NET 4 (zarówno dla C #, jak i VB.NET, chociaż VB.NET zawsze je miał). Oczywiście, możesz osiągnąć podobną funkcjonalność, po prostu używając właściwości, która może zostać ponownie przypisana przez konsumenta twojej klasy, ale nie uzyskasz korzyści z niezmienności zapewnianej przez przypisanie obiektu prywatnego interfejsu do parametru konstruktora.

Biorąc to wszystko pod uwagę, jeśli wprowadzasz nową zależność, która musi być dostarczona przez każdego konsumenta, będziesz musiał zrefaktoryzować swój konstruktor i cały kod konsumujący twoją klasę. Moje sugestie powyżej naprawdę mają zastosowanie tylko wtedy, gdy masz luksus możliwości zapewnienia domyślnej implementacji dla całego twojego obecnego kodu, ale nadal zapewniasz możliwość zastąpienia domyślnej implementacji, jeśli to konieczne.


1

To jest stary post, ale jeśli będzie potrzebny w przyszłości, może się przydać:

https://github.com/omegamit6zeichen/prinject

Miałem podobny pomysł i wymyśliłem ten framework. Prawdopodobnie jest daleki od ukończenia, ale jest to idea frameworka skupiającego się na wstrzykiwaniu właściwości


0

To zależy od tego, jak chcesz wdrożyć. Wolę wstrzykiwanie konstruktora wszędzie tam, gdzie czuję, że wartości, które wchodzą do implementacji, nie zmieniają się często. Np .: Jeśli strategia firmy jest zgodna z serwerem Oracle, skonfiguruję wartości źródła danych dla połączeń osiągających bean za pośrednictwem iniekcji konstruktora. W przeciwnym razie, jeśli moja aplikacja jest produktem i jest szansa, że ​​może połączyć się z dowolną bazą danych klienta, zaimplementowałbym taką konfigurację bazy danych i implementację wielu marek poprzez wstrzyknięcie setera. Właśnie wziąłem przykład, ale są lepsze sposoby realizacji scenariuszy, o których wspomniałem powyżej.


1
Nawet gdy koduję w firmie, zawsze koduję z punktu widzenia, że ​​jestem niezależnym wykonawcą i opracowuję kod przeznaczony do redystrybucji. Zakładam, że będzie to open source. W ten sposób upewniam się, że kod jest modułowy, wtykowy i zgodny z zasadami SOLID.
Fred,

0

Wstrzyknięcie konstruktora jawnie ujawnia zależności, dzięki czemu kod jest bardziej czytelny i mniej podatny na nieobsłużone błędy czasu wykonywania, jeśli argumenty są sprawdzane w konstruktorze, ale tak naprawdę sprowadza się to do osobistej opinii, a im więcej używasz DI, tym więcej będziesz mają tendencję do kołysania się w jedną lub drugą stronę, w zależności od projektu. Osobiście mam problemy z kodem, który pachnie jak konstruktory z długą listą argumentów i uważam, że konsument obiektu powinien znać zależności, aby mimo to użyć obiektu, więc jest to argument za użyciem wstrzykiwania właściwości. Nie podoba mi się niejawny charakter wstrzykiwania właściwości, ale uważam, że jest on bardziej elegancki, co skutkuje bardziej przejrzystym kodem. Z drugiej strony iniekcja konstruktora zapewnia wyższy stopień hermetyzacji,

Wybierz zastrzyk według konstruktora lub według właściwości mądrze w oparciu o konkretny scenariusz. I nie myśl, że musisz używać DI tylko dlatego, że wydaje się to konieczne i zapobiegnie to niewłaściwemu projektowi i zapachowi kodu. Czasami użycie wzoru nie jest warte wysiłku, jeśli wysiłek i złożoność przewyższają korzyści. Nie komplikuj.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.