Die Dependency Injection (DI) ist eine weit verbreitete Technik, die bei der Programmierung die für die Android-Entwicklung geeignet sind. Mit den Prinzipien von DI legen Sie den Grundstein dafür, für eine gute App-Architektur.
Die Implementierung einer Abhängigkeitsinjektion bietet folgende Vorteile:
- Wiederverwendbarkeit von Code
- Einfache Refaktorierung
- Einfache Tests
Grundlagen der Abhängigkeitsinjektion
Bevor wir speziell auf die Abhängigkeitsinjektion in Android eingehen, einen allgemeinen Überblick über die Funktionsweise der Abhängigkeitsinjektion.
Was ist Abhängigkeitsinjektion?
Klassen erfordern häufig Verweise auf andere Klassen. Beispiel: Eine Car
-Klasse
benötigt möglicherweise einen Verweis auf eine Engine
-Klasse. Diese erforderlichen Klassen werden als
dependencies. In diesem Beispiel ist die Car
-Klasse von
eine Instanz der Engine
-Klasse haben, die ausgeführt werden soll.
Es gibt drei Möglichkeiten, wie eine Klasse ein benötigtes Objekt abrufen kann:
- Die Klasse erstellt die Abhängigkeit, die sie benötigt. Im obigen Beispiel
Car
erstellt und initialisiert eine eigene Instanz vonEngine
. - Nimm ihn woandershin. Einige Android-APIs, z. B.
Context
Getter undgetSystemService()
, dies ändern - als Parameter bereitgestellt haben. Die App kann diese
Abhängigkeiten festlegen, wenn die Klasse konstruiert wird, oder sie an die Funktionen übergeben.
die jede Abhängigkeit benötigen. Im obigen Beispiel hat die
Car
-KonstruktorEngine
als Parameter erhalten würde.
Die dritte Option ist die Abhängigkeitsinjektion. Bei diesem Ansatz einer Klasse und geben diese an, anstatt die Klasse abfragen.
Hier ist ein Beispiel. Ohne Abhängigkeitsinjektion; stellt eine Car
dar, die
erstellt eine eigene Engine
-Abhängigkeit im Code wie folgt:
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(); } }
Dies ist kein Beispiel für eine Abhängigkeitsinjektion, da die Car
-Klasse
und eine eigene Engine
erstellen. Dies kann aus folgenden Gründen problematisch sein:
Car
undEngine
sind eng miteinander verbunden – eine Instanz vonCar
verwendet eine Typ vonEngine
. Unterklassen oder alternativen Implementierungen können verwendet. Wenn dieCar
ihr eigenesEngine
konstruieren würde, müssten Sie zwei Arten vonCar
, anstatt dieselbeCar
für Engines des TypsGas
undElectric
.Die starke Abhängigkeit von
Engine
erschwert das Testen.Car
verwendet eine echte Instanz vonEngine
. Daher können Sie kein test double zum Ändern vonEngine
für verschiedene Testläufe.
Wie sieht der Code mit Abhängigkeitsinjektion aus? Anstelle jeder Instanz
wenn Car
bei der Initialisierung ein eigenes Engine
-Objekt erstellt, erhält es eine
Engine
-Objekt als Parameter in seinem Konstruktor:
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(); } }
Die main
-Funktion verwendet Car
. Da Car
von Engine
abhängt, erstellt die Anwendung ein
Instanz von Engine
und verwendet sie dann, um eine Instanz von Car
zu erstellen. Die
Vorteile dieses DI-basierten Ansatzes:
Wiederverwendbarkeit von
Car
. Sie können verschiedene Implementierungen vonEngine
anCar
. Sie können beispielsweise eine neue abgeleitete Klasse vonEngine
mit dem NamenElectricEngine
, dieCar
verwenden soll. Wenn Sie DI nutzen, müssen Sie nur übergeben Sie eine Instanz der aktualisierten abgeleiteten KlasseElectricEngine
undCar
funktioniert weiterhin. ohne weitere Änderungen vorzunehmen.Einfache Tests von
Car
. Sie können Test-Doubles bestehen, um Ihre Szenarien durchführen. Sie könnten beispielsweise ein Test-Double vonEngine
mit dem NamenFakeEngine
und konfigurieren es für verschiedene Tests.
Es gibt zwei Möglichkeiten, die Abhängigkeitsinjektion in Android durchzuführen:
Konstruktor einschleusen Dies ist die oben beschriebene Methode. Sie bestehen am einer Klasse mit ihrem Konstruktor verknüpft.
Field Injection (oder Setter Injection). Bestimmte Android-Framework-Klassen Aktivitäten und Fragmente werden vom System instanziiert, sodass Konstruktoren eine Injektion nicht möglich. Mit Field Injection werden Abhängigkeiten instanziiert nachdem der Kurs erstellt wurde. Der Code würde so aussehen:
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(); } }
Automatisierte Abhängigkeitsinjektion
Im vorherigen Beispiel haben Sie die Abhängigkeiten erstellt, bereitgestellt und verwaltet.
Klassen erstellen, ohne auf eine Bibliothek angewiesen zu sein. Dies wird als
Abhängigkeitsinjektion manuell oder manuelle Abhängigkeitsinjektion Im Car
Beispiel: Es gab nur eine Abhängigkeit, aber mehr Abhängigkeiten und Klassen können
die manuelle Injektion von Abhängigkeiten
mühsamer machen. Manuelle Abhängigkeitsinjektion
bringt außerdem verschiedene Probleme mit sich:
Bei großen Anwendungen werden alle Abhängigkeiten zusammengenommen. kann eine große Menge Boilerplate-Code erfordern. In einem mehrschichtigen Architektur erstellen, müssen Sie zum Erstellen eines Objekts für eine oberste Ebene alle Abhängigkeiten der untergeordneten Schichten anzuzeigen. Um ein konkretes Beispiel zu erstellen, Bei einem echten Auto benötigen Sie möglicherweise einen Motor, ein Getriebe, ein Fahrgestell und andere Teile. und ein Motor benötigt Zylinder und Zündkerzen.
Wenn Sie keine Abhängigkeiten vor der Übergabe erstellen können – wenn Sie verzögerte Initialisierungen oder den Umfang von Objekten auf Abläufe app – Sie müssen einen benutzerdefinierten Container (oder eine Grafik von Abhängigkeiten), die die Lebensdauer der Abhängigkeiten im Arbeitsspeicher verwaltet.
Es gibt Bibliotheken, die dieses Problem lösen, indem sie den Prozess der Erstellen und Bereitstellen von Abhängigkeiten. Sie lassen sich in zwei Kategorien einteilen:
Reflexionsbasierte Lösungen, die Abhängigkeiten zur Laufzeit miteinander verbinden
Statische Lösungen, die den Code zum Verbinden von Abhängigkeiten generieren zur Kompilierungszeit.
Dagger ist eine beliebte Abhängigkeitsinjektionsbibliothek für Java. Kotlin und Android, die von Google verwaltet werden. Dolcher vereinfacht die Verwendung von DI in Ihrer App, indem Sie das Diagramm der Abhängigkeiten für Sie erstellen und verwalten. Es bietet vollständig statische Abhängigkeiten und Abhängigkeiten bei der Kompilierungszeit, die viele der Entwicklungs- und Leistungsprobleme bei reflexionsbasierten Lösungen, z. B. Guice:
Alternativen zur Abhängigkeitsinjektion
Eine Alternative zur Abhängigkeitsinjektion ist die Verwendung eines Service Locator ein. Das Designmuster für Service Locator verbessert die Entkopplung von Klassen von konkreten Abhängigkeiten. Sie erstellen einen Kurs. auch als Service Locator bezeichnet. Damit werden Abhängigkeiten erstellt und gespeichert. diese Abhängigkeiten bei Bedarf bereitstellt.
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(); } }
Das Service Locator-Muster unterscheidet sich von der Abhängigkeitsinjektion insofern, als die Elemente konsumiert werden. Mit dem Service Locator-Muster haben Klassen das Einschleusen von Objekten zu steuern; mit Abhängigkeitsinjektion die App die Kontrolle hat und die erforderlichen Objekte proaktiv einfügt.
Im Vergleich zur Abhängigkeitsinjektion:
Die Sammlung von Abhängigkeiten, die für eine Service Locator erforderlich sind, macht Code schwieriger zu testen, da alle Tests mit denselben globalen Service Locator verwenden.
Abhängigkeiten werden in der Klassenimplementierung codiert, nicht in der API-Oberfläche. Daher ist es schwerer zu erkennen, was eine Klasse von außen braucht. Daher ändern sich
Car
oder die in der Service Locator verfügbaren Abhängigkeiten können zur Laufzeit oder zum Test führen Fehler verursacht, da Referenzen fehlschlagen.Die Verwaltung der Lebensdauer von Objekten ist schwieriger, die Lebensdauer der App ab.
Hilt in deiner Android-App verwenden
Jetpack empfiehlt Hilt. für die Abhängigkeitsinjektion in Android. Hilt definiert eine Standardmethode für DI in Ihrer Anwendung, indem Sie Container für jede Android-Klasse in Ihrem und deren Lebenszyklen automatisch für Sie verwalten.
Hilt basiert auf der beliebten DI-Bibliothek Dagger, um von der Zeitrichtigkeit, Laufzeitleistung, Skalierbarkeit und Android Studio kompilieren von Dagger unterstützt.
Weitere Informationen zu Hilt findest du unter Dependency Injection with Hilt (Abhängigkeitsinjektion mit Hilt)
Fazit
Die Abhängigkeitsinjektion bietet Ihrer App die folgenden Vorteile:
Wiederverwendbarkeit von Klassen und Entkopplung von Abhängigkeiten: einfacherer Austausch Implementierungen einer Abhängigkeit. Verbesserte Wiederverwendung von Code durch Inversion und die Klassen kontrollieren nicht mehr, wie ihre Abhängigkeiten erstellt werden. Sie können aber mit jeder Konfiguration arbeiten.
Einfache Refaktorierung: Die Abhängigkeiten werden zu einem überprüfbaren Bestandteil der API. sodass sie bei der Objekterstellung oder bei der Kompilierung geprüft werden können. und werden nicht als Implementierungsdetails verborgen.
Einfache Tests: Eine Klasse verwaltet nicht ihre Abhängigkeiten. können Sie verschiedene Implementierungen verwenden, verschiedenen Fällen.
Um die Vorteile der Abhängigkeitsinjektion vollständig zu verstehen, sollten Sie sie ausprobieren wie unter Manuelle Abhängigkeitsinjektion beschrieben.
Weitere Informationen
Weitere Informationen zur Abhängigkeitsinjektion finden Sie in den folgenden zusätzlichen Ressourcen.