Jak korzystać z wtrysku zależności w połączeniu ze wzorem fabrycznym

10

Rozważ moduł odpowiedzialny za parsowanie plików dowolnego typu. Zastanawiam się nad wykorzystaniem wzorca strategii do rozwiązania tego problemu, jak już tu wyjaśniłem . Przed przejściem do tego pytania zapoznaj się z połączonym postem.

Rozważ klasę B, która potrzebuje zawartości pliku product.xml. Ta klasa będzie musiała utworzyć instancję odpowiedniego konkretnego implementatora interfejsu Parsera, aby przeanalizować plik XML. Mogę przekazać instancję odpowiedniego implementatora betonu do fabryki, tak aby klasa B miała „fabrykę”. Jednak klasa B będzie wówczas „zależeć” od fabryki w przypadku tworzenia konkretnej implementacji. Oznacza to, że konstruktor lub metoda ustawiająca w klasie B będzie musiała przejść przez fabrykę.

Dlatego fabryka i klasa B, które muszą parsować plik, będą ściśle ze sobą powiązane. Rozumiem, że mogę się całkowicie mylić co do tego, co wyjaśniłem do tej pory. Chciałbym wiedzieć, czy mogę zastosować wstrzykiwanie zależności w scenariuszu, w którym zależność, którą należy wstrzyknąć, to fabryka i jaki byłby właściwy sposób wdrożenia tego, aby móc skorzystać z takich obszarów, jak wyśmiewanie fabryki w moich testach jednostkowych.

CKing
źródło

Odpowiedzi:

8

Właściwy sposób to polegać na interfejsie, a następnie wprowadzić implementację tego interfejsu do klasy B.

Interfejs jest najcieńszą rzeczą, na której możesz polegać - porównuję go do próby złapania smugi dymu. Kod musi się z czymś powiązać, inaczej nic nie zrobi, ale sprzężenie z interfejsem jest tak rozdzielone, jak to tylko możliwe, ale interfejsy mogą zapewniać wszystkie potrzebne funkcje.

Niech więc konstruktor klasy B zastosuje interfejs do klasy, której potrzebuje, i niech fabryka wytworzy tę klasę jako implementator interfejsu. Nie polegaj na fabryce, polegaj na interfejsie i poproś fabrykę o wdrożenie tej fabryki.

Więc tak, będziesz używał zastrzyku zależności, ale nie ma w tym nic złego. Zastrzyk zależności - szczególnie prosty zastrzyk konstruktora - powinien być „normalnym” sposobem działania. Po prostu odłóż nowości tak daleko w swojej aplikacji (i jak najbliżej pierwszej linii main), jak to możliwe, i ukryj nowe połączenie w klasie zaprojektowanej specjalnie do tworzenia rzeczy.

Konkluzja: nie wahaj się wprowadzać zależności. To powinien być normalny sposób robienia rzeczy.

Nick Hodges
źródło
Czy wstrzykiwanie konstruktora odsuwa nowe rzeczy tak długo, jak to możliwe?
JeffO
To było źle - naprawiłem to, aby powiedzieć „tak daleko w aplikacji, jak to możliwe”.
Nick Hodges
Tak. Zastanawiałem się nad zależnością od interfejsu Parsera zamiast fabryki. Teraz, jeśli inna klasa używa klasy B, będzie musiała zależeć od interfejsu, od którego zależy klasa B. To będzie kontynuowane aż do najwyższej klasy. Ta klasa najwyższego poziomu będzie musiała w końcu użyć fabryki, aby utworzyć odpowiednią konkretną instancję analizatora składni. Czy klasa najwyższego poziomu powinna zależeć od fabryki, czy powinna używać fabryki bezpośrednio w ramach swoich metod?
CKing
1
Dobrze powiedziane: nie wahaj się wstrzykiwać zależności
Signcodeindie
9

Wydaje mi się, że twoja przesłanka jest tu trochę zagmatwana, mówisz o wstrzykiwaniu fabryki, ale wzorzec fabryczny to wzorzec kreacyjny, którego celem było zrobienie podzbioru tego, co robi struktura wstrzykiwania zależności, gdy szkielety DI nie były rozpowszechnione. przydatne z tego powodu. Jeśli jednak masz środowisko DI, tak naprawdę nie potrzebujesz już fabryki, ponieważ środowisko DI może spełnić cel, który fabryka spełniłaby.

To powiedziawszy, pozwól mi wyjaśnić trochę o wstrzykiwaniu zależności i jak ogólnie byś go użył.

Istnieje wiele sposobów wstrzykiwania zależności, ale najczęstsze to wstrzykiwanie konstruktora, wstrzykiwanie właściwości i bezpośredni DIContainer. Będę mówił o wstrzykiwaniu konstruktora, ponieważ wstrzykiwanie własności jest przez większość czasu niewłaściwym podejściem (właściwe podejście czasami), a dostęp do DIContainer nie jest preferowany, z wyjątkiem sytuacji, gdy absolutnie nie możesz wykonać żadnego z innych podejść.

Wtrysk konstruktora jest miejscem, w którym masz interfejs zależności i DIContainer (lub fabrykę), który zna konkretną implementację tej zależności, i gdziekolwiek potrzebujesz obiektu zależnego od tego interfejsu, w czasie budowy przekazujesz implementację z fabryki do to.

to znaczy

IDbConnectionProvider connProvider = DIContainer.Get<IDbConnectionProvider>();
IUserRepository userRepo = new UserRepository(connProvider);
User currentUser = userRepo.GetCurrentUser();

Wiele frameworków DI może znacznie to uprościć do tego, gdzie DIContainer sprawdzi konstruktora UserRepository pod kątem interfejsów, dla których zna konkretne implementacje, i automatycznie przekaże je za Ciebie; technika ta często nazywana jest Inwersją Kontroli, chociaż zarówno DI, jak i IoC są terminami, które często się wymieniają i mają niejasne (jeśli w ogóle) różnice.

Teraz, gdy zastanawiasz się, w jaki sposób nadrzędny kod uzyskuje dostęp do DIContainer, możesz mieć klasę statyczną do uzyskania dostępu lub bardziej odpowiednie jest to, że większość frameworków DI pozwala na nowe utworzenie DIContainer, przy czym tak naprawdę będzie on zachowywał się jak opakowanie do wewnętrznego słownika singletonów dla typów, o których wiadomo, że są konkretne dla danych interfejsów.

Oznacza to, że możesz odświeżyć DIContainer w dowolnym miejscu w kodzie i skutecznie uzyskać ten sam DIContainer, który już skonfigurowałeś, aby znać swoje relacje między interfejsem. Zwykłym sposobem ukrywania DIContainer przed częściami kodu, które nie powinny bezpośrednio z nim oddziaływać, jest po prostu upewnienie się, że tylko niezbędne projekty mają odniesienie do frameworku DI.

Jimmy Hoffa
źródło
Nie do końca zgadzam się z akapitem pierwszym. Nadal istnieją scenariusze, w których zależy się od fabryki (implementującej interfejs), która jest wstrzykiwana przez kontener DI.
Piotr Perak
Myślę, że przegapiłeś ten punkt, ponieważ OP nie mówił o ramach DI ani kontenerach DI.
Doc Brown
@Jimmy Hoffa Podczas gdy przedstawiłeś kilka bardzo interesujących kwestii, zdaję sobie sprawę z kontenerów IoC, takich jak Spring i koncepcja Dependency Injection. Moje obawy dotyczyły scenariusza, w którym nie używasz frameworka IoC, w którym to przypadku musisz napisać klasę, która utworzy dla ciebie twoje zależności. Jeśli klasa A zależy od interfejsu B, a klasa C używa klasy A, to klasa C zależy od interfejsu B. Ktoś musi nadać klasie C ce B. Czy klasa C powinna następnie zależeć od cla
CKing
Jeśli klasa A zależy od interfejsu B, a klasa C korzysta z klasy A, to klasa C zależy od interfejsu B. Ktoś musi nadać klasie C interfejs B. Czy klasa C powinna następnie zależeć od interfejsu B, czy też powinna zależeć od klasy, która utworzyła instancję zależności to znaczy fabryka.
Wygląda
@bot jak powiedziałem na wstępie, w dużej części można traktować fabryk jako podzbiór funkcjonalności kontenera DI, to nie jest prawdą we wszystkich scenariuszach, jak Doc Brown wspomniano, ale spojrzeć na moje próbki kodu i zastąpić DIContainerz DbConnectionFactoryi koncepcja nadal stoi na stanowisku, że pobrałbyś konkretną implementację ze swojego DI / Factory / etc i przekazałeś ją konsumentom tego typu w czasie ich budowy.
Jimmy Hoffa
5

Możesz przekazać fabrykę przez wstrzyknięcie zależności, tak jak przekazujesz cokolwiek innego, nie pozwól, aby rekurencyjność sytuacji myliła cię. Nie wiem, co jeszcze powiedzieć o jego wdrożeniu - już wiesz, jak zrobić wstrzykiwanie zależności.

Używam DI do wstrzykiwania fabryk dość regularnie.

psr
źródło
1
Jeśli klasa zależy od fabryki, będziesz musiał kpić z fabryki podczas jednostkowego testowania tej klasy. Jak ty to robisz? Czy klasa może zależeć od interfejsu fabrycznego?
CKing
4

Wstrzykiwanie fabryk nie jest niczym złym. Jest to standardowy sposób, jeśli podczas budowania obiektu nadrzędnego nie możesz zdecydować, jakiej zależności będziesz potrzebować. Myślę, że przykład najlepiej to wyjaśni. Użyję c #, ponieważ nie znam java.


class Parent
{
    private IParserFactory parserFactory;
    public Parent(IParserFactory factory)
    {
        parserFactory = factory
    }

    public ParsedObject ParseFrom(string filename, FileType fileType)
    {
        var parser = parserFactory.CreateFor(fileType);
        return parser.Parse(filename);
    }
}
Piotr Perak
źródło