Projektując system, często napotykam problem polegający na tym, że wiele modułów (logowanie, dostęp do bazy danych itp.) Jest używanych przez inne moduły. Pytanie brzmi: jak przejść do dostarczania tych komponentów innym komponentom. Dwie odpowiedzi wydają się możliwe wstrzyknięcie zależności lub użycie wzorca fabrycznego. Jednak oba wydają się błędne:
- Fabryki sprawiają, że testowanie jest uciążliwe i nie pozwala na łatwą wymianę implementacji. Nie ujawniają również zależności (np. Badasz metodę, nieświadomy faktu, że wywołuje metodę, która wywołuje metodę, która wywołuje metodę korzystającą z bazy danych).
- Wstrzyknięcie Dependecy ogromnie pęcznieje na listach argumentów konstruktora i rozmywa niektóre aspekty w całym kodzie. Typowa sytuacja ma miejsce, gdy wyglądają tak konstruktory o ponad połowie klas
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Oto typowa sytuacja, z którą mam problem: Mam klasy wyjątków, które używają opisów błędów ładowanych z bazy danych, używając zapytania, które ma parametr ustawienia języka użytkownika, który jest w obiekcie sesji użytkownika. Aby utworzyć nowy wyjątek, potrzebuję opisu, który wymaga sesji bazy danych i sesji użytkownika. Więc jestem skazany na przeciąganie tych wszystkich obiektów wszystkimi moimi metodami, na wypadek, gdybym musiał rzucić wyjątek.
Jak poradzić sobie z takim problemem?
Odpowiedzi:
Użyj wstrzykiwania zależności, ale ilekroć listy argumentów konstruktora stają się zbyt duże, refaktoryzuj je za pomocą usługi elewacji . Chodzi o to, aby zgrupować niektóre argumenty konstruktora, wprowadzając nową abstrakcję.
Na przykład, możesz wprowadzić nowy typ
SessionEnvironment
enkapsulujący aDBSessionProvider
, theUserSession
i załadowanyDescriptions
. Jednak, aby wiedzieć, które abstrakcje mają największy sens, należy znać szczegóły swojego programu.Podobne pytanie zostało już zadane tutaj na SO .
źródło
Z tego nie wydaje się, abyś rozumiał DI właściwie - chodzi o odwrócenie wzorca tworzenia obiektów w fabryce.
Twój konkretny problem wydaje się być bardziej ogólnym problemem związanym z OOP. Dlaczego obiekty nie mogą po prostu rzucać normalnych, nieczytelnych dla człowieka wyjątków podczas ich działania, a następnie mieć coś przed ostatnią próbą / chwytaniem, która przechwytuje ten wyjątek, i w tym momencie używa informacji o sesji, aby zgłosić nowy, ładniejszy wyjątek ?
Innym podejściem byłoby posiadanie fabryki wyjątków, która jest przekazywana do obiektów przez ich konstruktorów. Zamiast zgłosić nowy wyjątek, klasa może zgłosić metodę fabryczną (np
throw PrettyExceptionFactory.createException(data)
.Pamiętaj, że twoje obiekty, oprócz obiektów fabrycznych, nigdy nie powinny używać
new
operatora. Wyjątki są na ogół jednym szczególnym przypadkiem, ale w twoim przypadku mogą być wyjątkiem!źródło
Builder
wzorzec) dyktuje to. Jeśli przekazujesz parametry do swojego konstruktora, ponieważ obiekt tworzy instancję innych obiektów, jest to oczywisty przypadek dla IoC.Już dość dobrze wymieniłeś wady statycznego wzorca fabrycznego, ale nie całkiem zgadzam się z wadami wzorca wstrzykiwania zależności:
Wstrzyknięcie zależności wymaga, aby napisać kod dla każdej zależności, która nie jest błędem, ale cechą: Zmusza cię do zastanowienia się, czy naprawdę potrzebujesz tych zależności, promując w ten sposób luźne łączenie. W twoim przykładzie:
Nie, nie jesteś skazany. Dlaczego logika biznesowa odpowiada za lokalizację komunikatów o błędach dla konkretnej sesji użytkownika? Co jeśli w przyszłości chciałbyś skorzystać z tej usługi biznesowej z programu wsadowego (który nie ma sesji użytkownika ...)? A co jeśli komunikat o błędzie nie powinien być wyświetlany aktualnie zalogowanemu użytkownikowi, ale jego przełożonemu (który może preferować inny język)? A co, jeśli chcesz ponownie użyć logiki biznesowej na kliencie (który nie ma dostępu do bazy danych ...)?
Oczywiście lokalizowanie wiadomości zależy od tego, kto je przegląda, tj. Jest to odpowiedzialność warstwy prezentacji. Dlatego rzucałbym zwykłe wyjątki od usługi biznesowej, które niosą ze sobą identyfikator komunikatu, który można następnie wyszukać w module obsługi wyjątków warstwy prezentacji w dowolnym źródle wiadomości, którego używa.
W ten sposób możesz usunąć 3 niepotrzebne zależności (UserSession, ExceptionFactory i prawdopodobnie opisy), dzięki czemu Twój kod będzie zarówno prostszy, jak i bardziej uniwersalny.
Ogólnie rzecz biorąc, używałbym statycznych fabryk tylko do rzeczy, do których potrzebujesz wszechobecnego dostępu, i które są gwarantowane, że będą dostępne we wszystkich środowiskach, w których kiedykolwiek możemy chcieć uruchomić kod (np. Rejestrowanie). Do wszystkiego innego użyłbym zwykłego zastrzyku zależności.
źródło
Użyj iniekcji zależności. Korzystanie ze statycznych fabryk to wykorzystanie
Service Locator
antypatternu. Zobacz przełomową pracę Martina Fowlera tutaj - http://martinfowler.com/articles/injection.htmlJeśli argumenty konstruktora stają się zbyt duże i nie używasz kontenera DI, to napisz własne fabryki do tworzenia instancji, pozwalając na jego konfigurację przez XML lub powiązanie implementacji z interfejsem.
źródło
Ja też bym poszedł z Dependency Injection. Pamiętaj, że DI odbywa się nie tylko za pośrednictwem konstruktorów, ale także za pomocą ustawień właściwości. Na przykład rejestrator może zostać wstrzyknięty jako właściwość.
Możesz także użyć kontenera IoC, który może znieść część obciążenia, na przykład poprzez utrzymanie parametrów konstruktora na rzeczach potrzebnych w czasie wykonywania przez logikę domeny (utrzymywanie konstruktora w sposób, który ujawnia zamiar zależności klasowe i rzeczywiste) i może wstrzyknąć inne klasy pomocnicze poprzez właściwości.
Kolejnym krokiem, który możesz chcieć pójść, jest Programowanie zorientowane na aspekty, które jest wdrażane w wielu dużych ramach. Może to pozwolić ci przechwycić (lub „doradzić” użycie terminologii AspectJ) konstruktora klasy i wstrzyknąć odpowiednie właściwości, być może mając specjalny atrybut.
źródło
Nie do końca się zgadzam. Przynajmniej nie w ogóle.
Prosta fabryka:
Prosty zastrzyk:
Oba fragmenty służą temu samemu celowi, ustanawiają łącze między
IFoo
iFoo
. Cała reszta to tylko składnia.Zmiana
Foo
naADifferentFoo
dokładnie tyle samo wysiłku w każdej próbce kodu.Słyszałem, jak ludzie twierdzą, że wstrzykiwanie zależności pozwala na użycie różnych powiązań, ale ten sam argument można postawić na temat tworzenia różnych fabryk. Wybór odpowiedniego wiązania jest dokładnie tak samo złożony, jak wybór właściwej fabryki.
Metody fabryczne pozwalają np. Na użycie
Foo
w niektórych miejscach iADifferentFoo
innych miejscach. Niektórzy mogą nazywać to dobrym (przydatne, jeśli jest to potrzebne), niektórzy mogą nazywać to złym (możesz zrobić półnagą robotę, zastępując wszystko).Jednak nie jest tak trudno uniknąć tej dwuznaczności, jeśli trzymasz się jednej metody, która powraca,
IFoo
dzięki czemu zawsze masz jedno źródło. Jeśli nie chcesz strzelać w stopę, nie trzymaj załadowanego pistoletu lub nie celuj w stopę.Oto dlaczego niektórzy wolą jawnie odzyskiwać zależności w konstruktorze, tak jak to:
Słyszałem argumenty pro (bez wzdęcia konstruktora), słyszałem argumenty przeciw (użycie konstruktora umożliwia większą automatyzację DI).
Osobiście, choć poddałem się naszemu seniorowi, który chce użyć argumentów konstruktora, zauważyłem problem z listą rozwijaną w VS (w prawym górnym rogu, aby przejrzeć metody obecnej klasy), w której nazwy metod znikają z pola widzenia, gdy jeden sygnatury metody są dłuższe niż mój ekran (=> rozdęty konstruktor).
Z technicznego punktu widzenia nie obchodzi mnie to. Każda z tych opcji wymaga tyle samo wysiłku, by pisać. A ponieważ używasz DI, zwykle i tak nie wywołujesz ręcznie konstruktora. Ale błąd interfejsu Visual Studio sprawia, że wolę nie nadąć argumentu konstruktora.
Na marginesie: zastrzyk zależności i fabryki nie wykluczają się wzajemnie . Miałem przypadki, w których zamiast wstawiania zależności wstawiłem fabrykę, która generuje zależności (NInject na szczęście pozwala na użycie,
Func<IFoo>
więc nie trzeba tworzyć rzeczywistej klasy fabryki).Przypadki użycia tego są rzadkie, ale istnieją.
źródło
W tym przykładzie próbnym klasa fabryczna jest używana w czasie wykonywania w celu określenia, jaki rodzaj przychodzącego obiektu żądania HTTP ma zostać utworzony, na podstawie metody żądania HTTP. Do samej fabryki wstrzykiwany jest przykładowy pojemnik wstrzykiwania zależności. Umożliwia to fabryce określenie czasu działania i pozwala kontenerowi wstrzykiwania zależności obsługiwać zależności. Każdy przychodzący obiekt żądania HTTP ma co najmniej cztery zależności (superglobale i inne obiekty).
Kod klienta dla konfiguracji typu MVC w ramach scentralizowanego
index.php
może wyglądać następująco (pominięto sprawdzanie poprawności).Alternatywnie możesz usunąć statyczną naturę (i słowo kluczowe) fabryki i pozwolić inżektorowi zależności zarządzać całą rzeczą (stąd skomentowany konstruktor). Będziesz jednak musiał zmienić niektóre (nie stałe) odwołania
self
do elementów klasy ( ) na elementy składowe instancji ($this
).źródło