Często zadawane pytania o DI (FAQ)
- Czy DI to inna nazwa dla IoC?
- Co to jest Service Locator?
- Kiedy lepiej nie używać DI?
- Czy używanie DI ma swoje wady?
- Jak przepisać aplikację legacy na DI?
- Dlaczego preferuje się kompozycję nad dziedziczeniem?
- Czy można użyć Nette DI Container poza Nette?
- Dlaczego konfiguracja jest w plikach NEON?
- Czy parsowanie plików NEON nie spowalnia aplikacji?
- Jak dostać się z mojej klasy do parametrów w pliku konfiguracyjnym?
- Czy Nette obsługuje PSR-11: Container interface?
Czy DI to inna nazwa dla IoC?
Inversion of Control (IoC) to zasada skupiająca się na sposobie, w jaki kod jest uruchamiany – czy Twój kod
uruchamia obcy kod, czy Twój kod jest integrowany z obcym kodem, który go następnie wywołuje. IoC to szerokie pojęcie
obejmujące zdarzenia, tak zwaną zasadę Hollywood i inne aspekty. Częścią tej
koncepcji są również fabryki, o których mówi Reguła nr 3: zostaw to
fabryce, które stanowią inwersję dla operatora new
.
Dependency Injection (DI) skupia się na sposobie, w jaki jeden obiekt dowiaduje się o innym obiekcie, czyli o jego zależnościach. 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 z punktu widzenia czystości kodu. Na przykład do antywzorców należą techniki, które pracują z globalnym stanem lub tak zwany Service Locator.
Co to jest Service Locator?
Jest to alternatywa dla Dependency Injection. Działa tak, że tworzy centralne repozytorium, w którym rejestrowane są wszystkie dostępne usługi lub zależności. Kiedy obiekt potrzebuje zależności, prosi o nią Service Locator.
W porównaniu do Dependency Injection traci jednak na przejrzystości: zależności nie są przekazywane obiektom bezpośrednio i nie są tak łatwo identyfikowalne, co wymaga przeanalizowania kodu, aby wszystkie powiązania zostały odkryte i zrozumiane. Testowanie jest również bardziej skomplikowane, ponieważ nie możemy po prostu przekazywać obiektów mock do testowanych obiektów, ale musimy to robić przez Service Locator. Ponadto Service Locator narusza projekt kodu, ponieważ poszczególne obiekty muszą wiedzieć o jego istnieniu, co różni się od Dependency Injection, gdzie obiekty nie mają świadomości istnienia kontenera DI.
Kiedy lepiej nie używać DI?
Nie są znane żadne trudności związane z użyciem wzorca projektowego Dependency Injection. Wręcz przeciwnie, pobieranie zależności z globalnie dostępnych miejsc prowadzi do całego szeregu komplikacji, podobnie jak używanie Service Locatora. Dlatego warto zawsze korzystać z DI. To nie jest podejście dogmatyczne, ale po prostu nie znaleziono lepszej alternatywy.
Mimo to istnieją pewne sytuacje, w których nie przekazujemy sobie obiektów i pobieramy je z przestrzeni globalnej. Na przykład podczas debugowania kodu, gdy potrzebujesz w konkretnym punkcie programu wypisać wartość zmiennej, zmierzyć czas trwania określonej części programu lub zapisać komunikat. W takich przypadkach, gdy chodzi o tymczasowe czynności, które zostaną później usunięte z kodu, uzasadnione jest wykorzystanie globalnie dostępnego dumpera, stopera lub loggera. Te narzędzia bowiem nie należą do projektu kodu.
Czy używanie DI ma swoje wady?
Czy użycie Dependency Injection wiąże się z jakimiś wadami, takimi jak zwiększona pracochłonność pisania kodu lub pogorszona wydajność? Co tracimy, gdy zaczniemy pisać kod zgodnie z DI?
DI nie ma wpływu na wydajność ani zużycie pamięci aplikacji. Pewną rolę może odgrywać wydajność Kontenera DI, jednak w przypadku Nette DI kontener jest kompilowany do czystego PHP, więc jego narzut podczas działania aplikacji jest w zasadzie zerowy.
Podczas pisania kodu często konieczne jest tworzenie konstruktorów przyjmujących zależności. Kiedyś mogło to być czasochłonne, jednak dzięki nowoczesnym IDE i constructor property promotion jest to teraz kwestia kilku sekund. Fabryki można łatwo generować za pomocą Nette DI i wtyczki do PhpStorm jednym kliknięciem myszy. Z drugiej strony odpada potrzeba pisania singletonów i statycznych punktów dostępu.
Można stwierdzić, że poprawnie 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ą jedynie wyjęte z poszczególnych klas i przeniesione do nowych miejsc, czyli do kontenera DI i fabryk.
Jak przepisać aplikację legacy na DI?
Przejście z aplikacji legacy na Dependency Injection może być wymagającym procesem, zwłaszcza w przypadku dużych i złożonych aplikacji. Ważne jest, aby podchodzić do tego procesu systematycznie.
- Podczas przechodzenia na Dependency Injection ważne jest, aby wszyscy członkowie zespołu rozumieli zasady i procedury, które są stosowane.
- Najpierw przeprowadź analizę istniejącej aplikacji i zidentyfikuj kluczowe komponenty oraz 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, na przykład Nette DI.
- Stopniowo refaktoryzuj poszczególne części aplikacji, aby używały Dependency Injection. Może to obejmować modyfikacje konstruktorów lub metod tak, aby przyjmowały zależności jako parametry.
- Zmodyfikuj miejsca w kodzie, gdzie tworzone są obiekty z zależnościami, aby zamiast tego zależności były wstrzykiwane przez kontener. Może to obejmować użycie fabryk.
Pamiętaj, że przejście na Dependency Injection to inwestycja w jakość kodu i długoterminową utrzymywalność aplikacji. Chociaż przeprowadzenie tych zmian może być trudne, wynikiem powinien być czystszy, bardziej modularny i łatwo testowalny kod, który jest gotowy na przyszłe rozszerzenia i konserwację.
Dlaczego preferuje się kompozycję nad dziedziczeniem?
Lepiej jest używać kompozycji zamiast dziedziczenia, ponieważ służy ona do ponownego wykorzystania kodu, nie martwiąc się o konsekwencje zmian. Zapewnia więc luźniejsze powiązanie, dzięki czemu nie musimy się obawiać, że zmiana jakiegoś kodu spowoduje potrzebę zmiany innego zależnego kodu. Typowym przykładem jest sytuacja określana jako constructor hell.
Czy można użyć Nette DI Container poza Nette?
Zdecydowanie. Nette DI Container jest częścią Nette, ale został zaprojektowany jako samodzielna biblioteka, która może być używana niezależnie od pozostałych części frameworka. Wystarczy ją zainstalować za pomocą Composera, utworzyć plik konfiguracyjny z definicją Twoich usług, a następnie za pomocą kilku linii kodu PHP utworzyć kontener DI. I od razu możesz zacząć korzystać z zalet Dependency Injection w swoich projektach.
Jak wygląda konkretne użycie wraz z kodami opisuje rozdział Nette DI Container.
Dlaczego konfiguracja jest w plikach NEON?
NEON to prosty i łatwy do odczytania język konfiguracyjny, który został opracowany w ramach Nette do ustawiania aplikacji, usług i ich zależności. W porównaniu z JSONem lub YAMLem oferuje dla tego celu znacznie bardziej intuicyjne i elastyczne możliwości. W NEONie można naturalnie opisać powiązania, których w Symfony & YAMLu nie dałoby się zapisać albo w ogóle, albo tylko za pomocą skomplikowanego opisu.
Czy parsowanie plików NEON nie spowalnia aplikacji?
Chociaż pliki NEON parsują się bardzo szybko, ten aspekt w ogóle nie ma znaczenia. Powodem jest to, że parsowanie plików odbywa się tylko raz przy pierwszym uruchomieniu aplikacji. Następnie generowany jest kod kontenera DI, zapisywany na dysku i uruchamiany przy każdym kolejnym żądaniu, bez konieczności przeprowadzania dalszego parsowania.
Tak to działa w środowisku produkcyjnym. Podczas rozwoju pliki NEON są parsowane za każdym razem, gdy dojdzie do zmiany ich zawartości, aby programista miał zawsze aktualny kontener DI. Samo parsowanie jest, jak powiedziano, kwestią chwili.
Jak dostać się z mojej klasy do parametrów w pliku konfiguracyjnym?
Pamiętajmy o Regule nr 1: niech Ci to przekażą. Jeśli klasa wymaga informacji z pliku konfiguracyjnego, nie musimy zastanawiać się, jak się do tych informacji dostać, zamiast tego po prostu o nie prosimy – na przykład za pomocą konstruktora klasy. A przekazanie realizujemy w pliku konfiguracyjnym.
W tym przykładzie %myParameter%
jest symbolem zastępczym dla wartości parametru myParameter
,
który zostanie przekazany do konstruktora klasy MyClass
:
# config.neon
parameters:
myParameter: Some value
services:
- MyClass(%myParameter%)
Aby przekazywać więcej parametrów lub wykorzystać autowiring, warto opakować parametry w obiekt.
Czy Nette obsługuje PSR-11: Container interface?
Nette DI Container nie obsługuje bezpośrednio PSR-11. Jednakże, jeśli potrzebujesz interoperacyjności między Nette DI Containerem a bibliotekami lub frameworkami, które oczekują PSR-11 Container Interface, możesz utworzyć prosty adapter, który będzie służył jako most między Nette DI Containerem a PSR-11.