Najczęściej zadawane pytania dotyczące DI (FAQ)
- Czy DI to kolejna nazwa dla IoC?
- Czym jest Service Locator?
- Kiedy lepiej nie używać DI?
- Czy używanie DI ma swoje wady?
- Jak przepisać starszą aplikację na DI?
- Dlaczego kompozycja jest preferowana nad dziedziczeniem?
- Czy Nette DI Container może być używany poza Nette?
- Dlaczego konfiguracja znajduje się w plikach NEON?
- Czy parsowanie plików NEON spowalnia działanie aplikacji?
- Jak uzyskać dostęp do parametrów z pliku konfiguracyjnego w mojej klasie?
- Czy Nette obsługuje interfejs PSR-11 Container?
Czy DI to kolejna nazwa dla IoC?
Inversion of Control (IoC) to zasada skupiająca się na sposobie wykonywania kodu – czy twój kod inicjuje kod
zewnętrzny, czy też twój kod jest zintegrowany z kodem zewnętrznym, który następnie go wywołuje. IoC jest szeroką
koncepcją, która obejmuje zdarzenia, tzw. zasadę Hollywood i inne aspekty. Fabryki, które są
częścią zasady # 3:
Let the Factory Handle It, i reprezentują inwersję dla operatora new
, są również składnikami tej
koncepcji.
Dependency Injection (DI) dotyczy tego, jak jeden obiekt wie o innym obiekcie, czyli zależności. Jest to wzorzec projektowy, który wymaga jawnego przekazywania zależności między obiektami.
Można więc powiedzieć, że DI jest specyficzną formą IoC. Jednak nie wszystkie formy IoC są odpowiednie pod względem czystości kodu. Na przykład do anty-wzorców zaliczamy wszystkie techniki pracujące z globalnym stanem lub tzw. Service Locator.
Czym jest Service Locator?
Service Locator jest alternatywą dla Dependency Injection. Działa on poprzez stworzenie centralnego magazynu, w którym zarejestrowane są wszystkie dostępne usługi lub zależności. Kiedy obiekt potrzebuje zależności, żąda jej z lokalizatora usług.
Jednakże, w porównaniu z Dependency Injection, traci na przejrzystości: zależności nie są bezpośrednio przekazywane do obiektów i dlatego nie są łatwo identyfikowalne, co wymaga zbadania kodu, aby odkryć i zrozumieć wszystkie połączenia. Testowanie jest również bardziej skomplikowane, ponieważ nie możemy po prostu przekazać obiektów mock do testowanych obiektów, ale musimy przejść przez Service Locator. Ponadto Service Locator zakłóca projektowanie kodu, ponieważ poszczególne obiekty muszą być świadome jego istnienia, co różni się od Dependency Injection, gdzie obiekty nie mają wiedzy o kontenerze DI.
Kiedy lepiej nie używać DI?
Nie są znane trudności związane z używaniem wzorca projektowego Dependency Injection. Wręcz przeciwnie, uzyskiwanie zależności z globalnie dostępnych lokalizacji prowadzi do wielu komplikacji, podobnie jak używanie Service Locatora. Dlatego zaleca się, aby zawsze używać DI. Nie jest to podejście dogmatyczne, ale po prostu nie znaleziono lepszej alternatywy.
Istnieją jednak pewne sytuacje, w których nie przekazujemy sobie obiektów i nie uzyskujemy ich z przestrzeni globalnej. Na przykład podczas debugowania kodu i konieczności zrzucenia wartości zmiennej w określonym punkcie programu, zmierzenia czasu trwania pewnego fragmentu programu, czy też zalogowania komunikatu. W takich przypadkach, gdy chodzi o działania tymczasowe, które później zostaną usunięte z kodu, uzasadnione jest użycie globalnie dostępnego dumpera, stopera czy loggera. Narzędzia te nie należą przecież do projektu kodu.
Czy używanie DI ma swoje wady?
Czy używanie Dependency Injection wiąże się z jakimiś wadami, takimi jak zwiększenie złożoności pisania kodu lub gorsza wydajność? Co tracimy, gdy zaczynamy pisać kod zgodnie z DI?
DI nie ma wpływu na wydajność aplikacji ani na wymagania dotyczące pamięci. Wydajność kontenera DI może odgrywać pewną rolę, ale w przypadku Nette DI, kontener jest skompilowany do czystego PHP, więc jego narzut w czasie działania aplikacji jest w zasadzie zerowy.
Podczas pisania kodu konieczne jest tworzenie konstruktorów, które akceptują zależności. W przeszłości mogło to być czasochłonne, ale dzięki nowoczesnym IDE i promowaniu właściwości konstruktorów jest to obecnie kwestia kilku sekund. Fabryki można łatwo wygenerować za pomocą Nette DI i wtyczki PhpStorm za pomocą kilku kliknięć. Z drugiej strony nie ma potrzeby pisania singletonów i statycznych punktów dostępu.
Można stwierdzić, że prawidłowo zaprojektowana aplikacja wykorzystująca DI nie jest ani krótsza, ani dłuższa w porównaniu z aplikacją wykorzystującą singletony. Części kodu pracujące z zależnościami są po prostu wyodrębnione z poszczególnych klas i przeniesione w nowe miejsca, czyli do kontenera DI i fabryk.
Jak przepisać starszą aplikację na DI?
Migracja ze starszej aplikacji do Dependency Injection może być trudnym procesem, szczególnie w przypadku dużych i złożonych aplikacji. Ważne jest, aby podejść do tego procesu systematycznie.
- Podczas przechodzenia na Dependency Injection ważne jest, aby wszyscy członkowie zespołu rozumieli zasady i praktyki, które są używane.
- Najpierw przeprowadź analizę istniejącej aplikacji, aby zidentyfikować kluczowe komponenty i ich zależności. Stwórz plan, które części będą refaktoryzowane i w jakiej kolejności.
- Zaimplementuj kontener DI lub, jeszcze lepiej, użyj istniejącej biblioteki, takiej jak Nette DI.
- Stopniowo refaktoryzuj każdą część aplikacji, aby użyć Dependency Injection. Może to obejmować modyfikację konstruktorów lub metod, aby akceptowały zależności jako parametry.
- Zmodyfikuj miejsca w kodzie, gdzie tworzone są obiekty zależności, tak aby zależności były zamiast tego wstrzykiwane przez kontener. Może to obejmować użycie fabryk.
Pamiętaj, że przejście na Dependency Injection jest inwestycją w jakość kodu i długoterminową stabilność aplikacji. Chociaż wprowadzenie tych zmian może być wyzwaniem, rezultatem powinien być czystszy, bardziej modułowy i łatwo testowalny kod, który jest gotowy do przyszłych rozszerzeń i konserwacji.
Dlaczego kompozycja jest preferowana nad dziedziczeniem?
Preferowane jest użycie kompozycji zamiast dziedziczenia, ponieważ służy ona do ponownego wykorzystania kodu bez konieczności martwienia się o konsekwencje zmian. W ten sposób zapewnia luźniejsze sprzężenie, w którym nie musimy się martwić, że zmiana jakiegoś kodu spowoduje konieczność zmiany innego zależnego kodu. Typowym przykładem jest sytuacja określana jako piekło konstruktorów.
Czy Nette DI Container może być używany poza Nette?
Absolutnie. Nette DI Container jest częścią Nette, ale został zaprojektowany jako samodzielna biblioteka, która może być używana niezależnie od innych części frameworka. Wystarczy zainstalować ją za pomocą Composera, stworzyć plik konfiguracyjny definiujący Twoje usługi, a następnie użyć kilku linii kodu PHP do stworzenia kontenera DI. I możesz natychmiast zacząć wykorzystywać Dependency Injection w swoich projektach.
Rozdział Nette DI Container opisuje, jak wygląda konkretny przypadek użycia, łącznie z kodem.
Dlaczego konfiguracja znajduje się w plikach NEON?
NEON to prosty i czytelny język konfiguracyjny opracowany w ramach Nette, służący do konfigurowania aplikacji, usług i ich zależności. W porównaniu do JSON czy YAML, oferuje znacznie bardziej intuicyjne i elastyczne opcje w tym zakresie. W NEONie możesz naturalnie opisać wiązania, które w Symfony & YAML nie byłyby możliwe do napisania w ogóle lub tylko poprzez skomplikowany opis.
Czy parsowanie plików NEON spowalnia działanie aplikacji?
Chociaż pliki NEON są parsowane bardzo szybko, aspekt ten nie ma większego znaczenia. Powodem jest to, że parsowanie plików występuje tylko raz podczas pierwszego uruchomienia aplikacji. Następnie kod kontenera DI jest generowany, przechowywany na dysku i wykonywany dla każdego kolejnego żądania bez potrzeby dalszego parsowania.
Tak właśnie działa to w środowisku produkcyjnym. Podczas tworzenia aplikacji pliki NEON są parsowane za każdym razem, gdy zmienia się ich zawartość, co zapewnia, że programista ma zawsze aktualny kontener DI. Jak wspomniano wcześniej, faktyczne parsowanie jest kwestią chwili.
Jak uzyskać dostęp do parametrów z pliku konfiguracyjnego w mojej klasie?
Należy pamiętać o regule #1: Let It Be Passed to You. Jeśli klasa wymaga informacji z pliku konfiguracyjnego, nie musimy wymyślać jak uzyskać dostęp do tych informacji; zamiast tego, po prostu o nie prosimy – na przykład poprzez konstruktor klasy. A my wykonujemy przekazywanie informacji w pliku konfiguracyjnym.
W tym przykładzie, %myParameter%
to placeholder dla wartości parametru myParameter
, który zostanie
przekazany do konstruktora MyClass
:
# config.neon
parameters:
myParameter: Some value
services:
- MyClass(%myParameter%)
Jeśli chcesz przekazać wiele parametrów lub użyć autowiring, warto zawinąć parametry w obiekt.
Czy Nette obsługuje interfejs PSR-11 Container?
Nette DI Container nie obsługuje bezpośrednio PSR-11. Jeśli jednak potrzebujesz interoperacyjności między Nette DI Container a bibliotekami lub frameworkami, które oczekują interfejsu PSR-11 Container, możesz stworzyć prosty adapter, który posłuży jako most między Nette DI Container a PSR-11.