Wstrzykiwanie zależności to technika powszechnie używana w programowaniu i dobrze nadająca się do programowania na Androida. Postępując zgodnie z zasadami DI, tworzysz podstawy dobrej architektury aplikacji.
Wdrożenie wstrzykiwania zależności przynosi takie korzyści:
- Możliwość ponownego wykorzystania kodu
- Łatwość refaktoryzacji
- Łatwość testowania
Podstawy wstrzykiwania zależności
Zanim omówimy wstrzykiwanie zależności na urządzeniach z Androidem, znajdziesz na tej stronie ogólne informacje o tym, jak działa wstrzykiwanie zależności.
Co to jest wstrzykiwanie zależności?
Zajęcia często wymagają odwołań do innych klas. Na przykład klasa Car
może wymagać odwołania do klasy Engine
. Te wymagane klasy są nazywane zależnościami, a w tym przykładzie klasa Car
zależy od tego, czy istnieje instancja klasy Engine
, która ma zostać uruchomiona.
Klasa może pobrać potrzebny obiekt na 3 sposoby:
- Klasa tworzy zależność, której potrzebuje. W podanym wyżej przykładzie
Car
utworzy i zainicjuje własną instancjęEngine
. - Pobierz gdzie indziej W ten sposób działają niektóre interfejsy API Androida, takie jak pobieranie
Context
igetSystemService()
. - Podaj go jako parametr. Aplikacja może udostępniać te zależności podczas tworzenia klasy lub przekazywać je do funkcji, które wymagają danej zależności. W powyższym przykładzie konstruktor
Car
otrzymałby jako parametrEngine
.
Trzecia opcja to wstrzykiwanie zależności. W tym podejściu zależności klas są udostępniane, a nie odczytywane przez instancję klasy.
Oto przykład. Bez wstrzykiwania zależności reprezentowanie w kodzie Car
, który tworzy własną zależność Engine
, wygląda tak:
Kotlin
class Car { private val engine = Engine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
Java
class Car { private Engine engine = new Engine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
![Klasa samochodu bez wstrzykiwania zależności](https://developer.android.com/static/images/training/dependency-injection/1-car-engine-no-di.png?authuser=9&hl=pl)
To nie jest przykład wstrzykiwania zależności, ponieważ klasa Car
konstruuje własną klasę Engine
. Może to stanowić problem, ponieważ:
Car
iEngine
są ściśle sprzężone – wystąpienie obiektuCar
używa jednego typu obiektuEngine
i nie można łatwo używać podklas ani alternatywnych implementacji. Gdyby obiektCar
skonstruował własny obiektEngine
, konieczne byłoby utworzenie 2 typów obiektuCar
, zamiast ponownego używania tego samego obiektuCar
w wyszukiwarkach typuGas
iElectric
.Twarda zależność od
Engine
utrudnia testowanie. FunkcjaCar
używa prawdziwego wystąpieniaEngine
, co uniemożliwia użycie podwójnej wartości testowej do modyfikowaniaEngine
w różnych przypadkach testowych.
Jak wygląda kod z wstrzykiwaniem zależności? Zamiast każde wystąpienie Car
konstruujące przy zainicjowaniu własny obiekt Engine
otrzymuje obiekt Engine
jako parametr w swoim konstruktorze:
Kotlin
class Car(private val engine: Engine) { fun start() { engine.start() } } fun main(args: Array) { val engine = Engine() val car = Car(engine) car.start() }
Java
class Car { private final Engine engine; public Car(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Engine engine = new Engine(); Car car = new Car(engine); car.start(); } }
![Klasa samochodu korzystająca z wstrzykiwania zależności](https://developer.android.com/static/images/training/dependency-injection/1-car-engine-di.png?authuser=9&hl=pl)
Funkcja main
używa funkcji Car
. Ponieważ funkcja Car
zależy od parametru Engine
, aplikacja tworzy instancję Engine
, a potem używa jej do utworzenia instancji Car
. Zalety tego podejścia opartego na danych:
Możliwość wielokrotnego korzystania z aplikacji
Car
. Do usługiCar
możesz przekazywać różne implementacje koduEngine
. Możesz na przykład zdefiniować nową podklasę klasyEngine
o nazwieElectricEngine
, której ma używać usługaCar
. Jeśli używasz DI, musisz tylko przekazać w instancji zaktualizowanej podklasyElectricEngine
, aCar
będzie nadal działać bez wprowadzania zmian.Łatwe testowanie aplikacji
Car
. Możesz zdać egzaminy podwójne, aby sprawdzić w różnych sytuacjach. Możesz na przykład utworzyć testowy duplikat elementuEngine
o nazwieFakeEngine
i skonfigurować go na potrzeby różnych testów.
Istnieją 2 główne sposoby wstrzykiwania zależności na Androidzie:
Constructor Injection (Wstrzykiwanie konstruktora). w sposób opisany powyżej. Przekazujesz zależności klasy do jej konstruktora.
Wstrzyknięcie pola (lub Setter Injection). Niektóre klasy platformy Androida, takie jak działania i fragmenty, są tworzone przez system, dlatego wstrzykiwanie konstruktora nie jest możliwe. W przypadku wstrzykiwania pól instancje są tworzone po utworzeniu klasy. Kod będzie wyglądał tak:
Kotlin
class Car { lateinit var engine: Engine fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.engine = Engine() car.start() }
Java
class Car { private Engine engine; public void setEngine(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.setEngine(new Engine()); car.start(); } }
Automatyczne wstrzykiwanie zależności
W poprzednim przykładzie udało Ci się samodzielnie utworzyć i udostępnić zależności różnych klas oraz zarządzać nimi bez konieczności korzystania z biblioteki. Jest to tzw. ręczne wstrzykiwanie zależności lub ręczne wstrzykiwanie zależności. W przykładzie Car
występuje tylko jedna zależność, ale większa liczba zależności i klas może utrudniać ręczne wstrzykiwanie zależności. Ręczne wstrzykiwanie zależności
również wiąże się z kilkoma problemami:
W przypadku dużych aplikacji prawidłowe podłączenie wszystkich zależności może wymagać obszernego kodu. W architekturze wielowarstwowej, aby utworzyć obiekt dla górnej warstwy, trzeba podać wszystkie zależności znajdujących się pod nią warstw. Konkretny przykład: do zbudowania prawdziwego samochodu mogą być potrzebne silnik, skrzynia biegów, podwozia i inne części. Silnik z kolei potrzebuje cylindrów i świeczek zapłonowych.
Jeśli nie możesz utworzyć zależności przed ich przekazaniem – na przykład w przypadku korzystania z leniwego inicjowania lub określania zakresu obiektów w przepływach aplikacji – musisz utworzyć i obsługiwać kontener (lub wykres zależności), który będzie zarządzać czasem trwania zależności w pamięci.
Istnieją biblioteki, które rozwiązują ten problem, automatyzując proces tworzenia i udostępniania zależności. Można je podzielić na 2 kategorie:
Rozwiązania oparte na odczuciach, które łączą zależności w czasie działania.
Rozwiązania statyczne, które generują kod łączący zależności podczas kompilacji.
Dagger to obsługiwana przez Google popularna biblioteka do wstrzykiwania zależności w językach Java, Kotlin i Android. Dagger ułatwia korzystanie z DI w aplikacji, tworząc wykres zależności i nim zarządzając. Zapewnia w pełni statyczne i czasowe kompilacje rozwiązania problemów z rozwojem i wydajnością rozwiązań opartych na odczuciach, takich jak Guice.
Alternatywy dla wstrzykiwania zależności
Alternatywą dla wstrzykiwania zależności jest użycie lokalizatora usług. Wzorzec projektu lokalizatora usług poprawia również oddzielenie klas od konkretnych zależności. Tworzysz klasę o nazwie lokalizator usług, która tworzy i przechowuje zależności, a następnie udostępnia te zależności na żądanie.
Kotlin
object ServiceLocator { fun getEngine(): Engine = Engine() } class Car { private val engine = ServiceLocator.getEngine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
Java
class ServiceLocator { private static ServiceLocator instance = null; private ServiceLocator() {} public static ServiceLocator getInstance() { if (instance == null) { synchronized(ServiceLocator.class) { instance = new ServiceLocator(); } } return instance; } public Engine getEngine() { return new Engine(); } } class Car { private Engine engine = ServiceLocator.getInstance().getEngine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
Wzorzec lokalizatora usług różni się od wstrzykiwania zależności w sposobie korzystania z elementów. Wzorzec lokalizatora usług daje klasom kontrolę i prośby o wstrzykiwanie obiektów. Dzięki wstrzykiwaniu zależności aplikacja ma kontrolę i aktywnie wprowadza wymagane obiekty.
W porównaniu z wstrzykiwaniem zależności:
Gromadzenie zależności wymaganych przez lokalizator usług utrudnia testowanie kodu, ponieważ wszystkie testy muszą współdziałać z tym samym globalnym lokalizatorem usług.
Zależności są kodowane w implementacji klas, a nie na powierzchni interfejsu API. Dlatego trudniej jest określić, czego dana klasa potrzebuje z zewnątrz. W związku z tym zmiany w
Car
lub zależności dostępne w lokalizatorze usług mogą powodować błędy w środowisku wykonawczym lub testów, ponieważ odwołania do nich nie łamią.Zarządzanie czasem trwania obiektów jest trudniejsze, jeśli chcesz uwzględnić coś innego niż okres użytkowania całej aplikacji.
Korzystanie z Hilt w aplikacji na Androida
Hilt to zalecana biblioteka Jetpack do wstrzykiwania zależności na Androidzie. Hilt definiuje standardowy sposób wykonywania operacji typu DI w aplikacji, udostępniając kontenery dla każdej klasy Androida w projekcie i automatycznie zarządzając ich cyklami życia.
Hilt działa w oparciu o popularną bibliotekę DI Dagger, aby korzystać z poprawności czasu kompilacji, wydajności środowiska wykonawczego, skalowalności i obsługi Android Studio zapewnianej przez Dagger.
Więcej informacji o Hilt znajdziesz w artykule o wstrzykiwaniu zależności za pomocą Hilt.
Podsumowanie
Wstrzykiwanie zależności zapewnia aplikacji takie korzyści:
Ponowne wykorzystanie klas i rozłączenie zależności: łatwiejsza zamiana implementacji zależności. Ponowne wykorzystanie kodu jest łatwiejsze dzięki odwróceniu kontroli – klasy nie kontrolują już sposobu tworzenia zależności, ale działają z dowolną konfiguracją.
Łatwość refaktoryzacji: zależności stają się możliwym do zweryfikowania częścią interfejsu API, więc można je sprawdzić w czasie tworzenia obiektu lub kompilacji, zamiast być ukryte jako szczegóły implementacji.
Łatwość testowania: klasa nie zarządza swoimi zależnościami, więc podczas testowania możesz ją przetestować w różnych implementacjach, aby przetestować różne przypadki.
Aby w pełni poznać korzyści wstrzykiwania zależności, wypróbuj tę funkcję ręcznie w aplikacji, zgodnie z opisem w sekcji Ręczne wstrzykiwanie zależności.
Dodatkowe materiały
Więcej informacji o wstrzykiwaniu zależności znajdziesz w dodatkowych materiałach poniżej.