Abhängigkeitsinjektion in Android

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:

  1. Die Klasse erstellt die Abhängigkeit, die sie benötigt. Im obigen Beispiel Car erstellt und initialisiert eine eigene Instanz von Engine.
  2. Nimm ihn woandershin. Einige Android-APIs, z. B. Context Getter und getSystemService(), dies ändern
  3. 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 -Konstruktor Engine 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();
    }
}
Autoklasse ohne Abhängigkeitsinjektion

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 und Engine sind eng miteinander verbunden – eine Instanz von Car verwendet eine Typ von Engine. Unterklassen oder alternativen Implementierungen können verwendet. Wenn die Car ihr eigenes Engine konstruieren würde, müssten Sie zwei Arten von Car, anstatt dieselbe Car für Engines des Typs Gas und Electric.

  • Die starke Abhängigkeit von Engine erschwert das Testen. Car verwendet eine echte Instanz von Engine. Daher können Sie kein test double zum Ändern von Engine 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();
    }
}
Autoklasse mit Abhängigkeitsinjektion

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 von Engine an Car. Sie können beispielsweise eine neue abgeleitete Klasse von Engine mit dem Namen ElectricEngine, die Car verwenden soll. Wenn Sie DI nutzen, müssen Sie nur übergeben Sie eine Instanz der aktualisierten abgeleiteten Klasse ElectricEngine und Car 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 von Engine mit dem Namen FakeEngine 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.

Produktproben