Dependency Injection

Hinweis

Dieses E-Book wurde im Frühjahr 2017 veröffentlicht und seitdem nicht mehr aktualisiert. Es gibt vieles im Buch, das wertvoll bleibt, aber ein Teil des Materials ist veraltet.

In der Regel wird beim Instanziieren eines Objekts ein Klassenkonstruktor aufgerufen, und alle Werte, die das Objekt benötigt, werden als Argumente an den Konstruktor übergeben. Dies ist ein Beispiel für die Abhängigkeitsinjektion und wird insbesondere als Konstruktorinjektion bezeichnet. Die Abhängigkeiten, die das Objekt benötigt, werden in den Konstruktor eingefügt.

Durch die Angabe von Abhängigkeiten als Schnittstellentypen ermöglicht die Abhängigkeitsinjektion die Entkopplung der konkreten Typen vom Code, der von diesen Typen abhängt. Im Allgemeinen wird ein Container verwendet, der eine Liste von Registrierungen und Zuordnungen zwischen Schnittstellen und abstrakten Typen sowie die konkreten Typen enthält, die diese Typen implementieren oder erweitern.

Es gibt auch andere Arten von Abhängigkeitsinjektion, z. B. Die Einschleusung von Eigenschaftensettern und die Injektion von Methodenaufrufen, die jedoch seltener vorkommen. Daher konzentriert sich dieses Kapitel ausschließlich auf die Durchführung von Konstruktorinjektion mit einem Dependency Injection-Container.

Einführung in die Abhängigkeitsinjektion

Dependency Injection ist eine spezielle Version des IoC-Musters (Inversion of Control), bei dem das invertierte Problem der Prozess zum Abrufen der erforderlichen Abhängigkeit ist. Mit Dependency Injection ist eine andere Klasse für das Einfügen von Abhängigkeiten in ein Objekt zur Laufzeit verantwortlich. Das folgende Codebeispiel zeigt, wie die ProfileViewModel-Klasse strukturiert ist, wenn Dependency Injection verwendet wird:

public class ProfileViewModel : ViewModelBase  
{  
    private IOrderService _orderService;  

    public ProfileViewModel(IOrderService orderService)  
    {  
        _orderService = orderService;  
    }  
    ...  
}

Der ProfileViewModel Konstruktor empfängt ein IOrderService instance als Argument, das von einer anderen Klasse eingefügt wird. Die einzige Abhängigkeit in der ProfileViewModel -Klasse ist vom Schnittstellentyp. Daher verfügt die ProfileViewModel Klasse nicht über Kenntnisse der Klasse, die für die Instanziierung des IOrderService Objekts verantwortlich ist. Die Klasse, die für die Instanziierung des IOrderService Objekts und das Einfügen in die ProfileViewModel -Klasse verantwortlich ist, wird als Dependency Injection-Container bezeichnet.

Container mit Abhängigkeitsinjektion verringern die Kopplung zwischen Objekten, indem sie eine Möglichkeit zum Instanziieren von Klasseninstanzen bereitstellen und deren Lebensdauer basierend auf der Konfiguration des Containers verwalten. Während der Objekterstellung fügt der Container alle Abhängigkeiten ein, die das Objekt benötigt. Wenn diese Abhängigkeiten noch nicht erstellt wurden, erstellt der Container zuerst die Abhängigkeiten und löst sie auf.

Hinweis

Abhängigkeitsinjektion kann auch manuell mithilfe von Fabriken implementiert werden. Die Verwendung eines Containers bietet jedoch zusätzliche Funktionen wie die Verwaltung der Lebensdauer und die Registrierung durch Assemblyscans.

Die Verwendung eines Dependency Injection-Containers hat mehrere Vorteile:

  • Ein Container entfällt die Notwendigkeit, dass eine Klasse ihre Abhängigkeiten suchen und deren Lebensdauer verwalten muss.
  • Ein Container ermöglicht die Zuordnung implementierter Abhängigkeiten, ohne dass sich dies auf die Klasse auswirkt.
  • Ein Container erleichtert die Testbarkeit, indem Abhängigkeiten simuliert werden können.
  • Ein Container erhöht die Wartbarkeit, indem der App neue Klassen auf einfache Weise hinzugefügt werden können.

Im Kontext einer Xamarin.Forms App, die MVVM verwendet, wird ein Abhängigkeitsinjektionscontainer in der Regel zum Registrieren und Auflösen von Ansichtsmodellen sowie zum Registrieren von Diensten und zum Einfügen dieser Injektion in Ansichtsmodelle verwendet.

Es sind viele Container für die Einschleusung von Abhängigkeiten verfügbar, wobei die mobile eShopOnContainers-App TinyIoC verwendet, um die Instanziierung von Ansichtsmodell und Dienstklassen in der App zu verwalten. TinyIoC wurde nach der Bewertung einer Reihe verschiedener Container ausgewählt und bietet eine bessere Leistung auf mobilen Plattformen im Vergleich zu den meisten bekannten Containern. Es erleichtert das Erstellen von lose gekoppelten Apps und bietet alle Features, die häufig in Abhängigkeitsinjektionscontainern zu finden sind, einschließlich Methoden zum Registrieren von Typzuordnungen, Auflösen von Objekten, Verwalten von Objektlebensdauern und Einfügen abhängiger Objekte in Konstruktoren von Objekten, die aufgelöst werden. Weitere Informationen zu TinyIoC finden Sie unter TinyIoC auf github.com.

In TinyIoC stellt der TinyIoCContainer Typ den Dependency Injection-Container bereit. Abbildung 3-1 zeigt die Abhängigkeiten bei verwendung dieses Containers, der ein IOrderService Objekt instanziiert und in die ProfileViewModel Klasse einschleust.

Beispiel für Abhängigkeiten bei Verwendung der Abhängigkeitsinjektion

Abbildung 3-1: Abhängigkeiten bei Verwendung von Abhängigkeitsinjektion

Zur Laufzeit muss der Container wissen, welche Implementierung der IOrderService Schnittstelle instanziieren soll, bevor er ein ProfileViewModel -Objekt instanziieren kann. Dies erfordert:

  • Der Container, der entscheidet, wie ein Objekt instanziiert werden soll, das die IOrderService -Schnittstelle implementiert. Dies wird als Registrierung bezeichnet.
  • Der Container, der das Objekt instanziiert, das die IOrderService Schnittstelle implementiert, und das ProfileViewModel -Objekt. Dies wird als Auflösung bezeichnet.

Schließlich wird die App das -Objekt verwenden und für die ProfileViewModel Garbage Collection verfügbar sein. An diesem Punkt sollte der Garbage Collector die IOrderService instance verwerfen, wenn andere Klassen nicht dieselbe instance.

Tipp

Schreiben sie containerunabhängigen Code. Versuchen Sie immer, containerunabhängigen Code zu schreiben, um die App vom jeweiligen verwendeten Abhängigkeitscontainer zu entkoppeln.

Registrierung

Bevor Abhängigkeiten in ein Objekt eingefügt werden können, müssen die Typen der Abhängigkeiten zuerst beim Container registriert werden. Das Registrieren eines Typs umfasst in der Regel die Übergabe einer Schnittstelle und eines konkreten Typs, der die Schnittstelle implementiert.

Es gibt zwei Möglichkeiten, Typen und Objekte im Container über Code zu registrieren:

  • Registrieren Sie einen Typ oder eine Zuordnung beim Container. Bei Bedarf erstellt der Container eine Instanz des angegebenen Typs.
  • Registrieren Sie ein vorhandenes Objekt im Container als Singleton. Bei Bedarf gibt der Container einen Verweis auf das vorhandene Objekt zurück.

Tipp

Dependency Injection-Container sind nicht immer geeignet. Dependency Injection führt zu zusätzlicher Komplexität und Anforderungen, die für kleine Apps möglicherweise nicht geeignet oder nützlich sind. Wenn eine Klasse keine Abhängigkeiten aufweist oder keine Abhängigkeit für andere Typen ist, ist es möglicherweise nicht sinnvoll, sie im Container zu platzieren. Wenn eine Klasse über einen einzelnen Satz von Abhängigkeiten verfügt, die für den Typ integral sind und sich nie ändern, ist es möglicherweise nicht sinnvoll, sie im Container zu platzieren.

Die Registrierung von Typen, die eine Abhängigkeitsinjektion erfordern, sollte in einer einzelnen Methode in einer App durchgeführt werden, und diese Methode sollte zu einem frühen Zeitpunkt im Lebenszyklus der App aufgerufen werden, um sicherzustellen, dass die App die Abhängigkeiten zwischen ihren Klassen kennt. In der mobilen eShopOnContainers-App wird dies von der ViewModelLocator -Klasse ausgeführt, die das TinyIoCContainer -Objekt erstellt und die einzige Klasse in der App ist, die einen Verweis auf dieses Objekt enthält. Das folgende Codebeispiel zeigt, wie die mobile eShopOnContainers-App das TinyIoCContainer Objekt in der ViewModelLocator -Klasse deklariert:

private static TinyIoCContainer _container;

Typen werden im ViewModelLocator Konstruktor registriert. Dies wird erreicht, indem zuerst ein TinyIoCContainer instance erstellt wird, was im folgenden Codebeispiel veranschaulicht wird:

_container = new TinyIoCContainer();

Typen werden dann mit dem TinyIoCContainer -Objekt registriert, und das folgende Codebeispiel veranschaulicht die häufigste Form der Typregistrierung:

_container.Register<IRequestProvider, RequestProvider>();

Die Register hier gezeigte Methode ordnet einen Schnittstellentyp einem konkreten Typ zu. Standardmäßig ist jede Schnittstellenregistrierung als Singleton konfiguriert, sodass jedes abhängige Objekt die gleichen freigegebenen instance erhält. Daher ist nur ein einzelner RequestProvider instance im Container vorhanden, der von Objekten gemeinsam genutzt wird, die eine Einschleusung eines IRequestProvider über einen Konstruktor erfordern.

Konkrete Typen können auch direkt ohne Zuordnung aus einem Schnittstellentyp registriert werden, wie im folgenden Codebeispiel gezeigt:

_container.Register<ProfileViewModel>();

Standardmäßig wird jede konkrete Klassenregistrierung als multi-instance konfiguriert, sodass jedes abhängige Objekt eine neue instance erhält. Daher wird beim Auflösen von ProfileViewModel eine neue instance erstellt, und der Container fügt die erforderlichen Abhängigkeiten ein.

Lösung

Nachdem ein Typ registriert wurde, kann er aufgelöst oder als Abhängigkeit eingefügt werden. Wenn ein Typ aufgelöst wird und der Container eine neue instance erstellen muss, fügt er alle Abhängigkeiten in die instance ein.

Wenn ein Typ aufgelöst wird, geschieht im Allgemeinen eines von drei Dingen:

  1. Wenn der Typ nicht registriert wurde, löst der Container eine Ausnahme aus.
  2. Wenn der Typ als Singleton registriert wurde, gibt der Container die Singleton-Instanz zurück. Wenn der Typ zum ersten Mal aufgerufen wird, erstellt der Container ihn bei Bedarf und verwaltet einen Verweis darauf.
  3. Wenn der Typ nicht als Singleton registriert wurde, gibt der Container eine neue instance zurück und verwaltet keinen Verweis darauf.

Das folgende Codebeispiel zeigt, wie der RequestProvider Zuvor bei TinyIoC registrierte Typ aufgelöst werden kann:

var requestProvider = _container.Resolve<IRequestProvider>();

In diesem Beispiel wird TinyIoC aufgefordert, den konkreten Typ für den IRequestProvider Typ zusammen mit allen Abhängigkeiten aufzulösen. In der Regel wird die Resolve -Methode aufgerufen, wenn eine instance eines bestimmten Typs erforderlich ist. Informationen zum Steuern der Lebensdauer aufgelöster Objekte finden Sie unter Verwalten der Lebensdauer aufgelöster Objekte.

Das folgende Codebeispiel zeigt, wie die mobile eShopOnContainers-App Ansichtsmodelltypen und deren Abhängigkeiten instanziiert:

var viewModel = _container.Resolve(viewModelType);

In diesem Beispiel wird TinyIoC aufgefordert, den Ansichtsmodelltyp für ein angefordertes Ansichtsmodell aufzulösen, und der Container löst auch alle Abhängigkeiten auf. Beim Auflösen des ProfileViewModel Typs sind die aufzulösenden Abhängigkeiten ein ISettingsService -Objekt und ein IOrderService -Objekt. Da Schnittstellenregistrierungen beim Registrieren der SettingsService Klassen und OrderService verwendet wurden, gibt TinyIoC Singletoninstanzen für die SettingsService Klassen und zurück OrderService und übergibt sie dann an den Konstruktor der ProfileViewModel Klasse. Weitere Informationen dazu, wie die mobile eShopOnContainers-App Ansichtsmodelle erstellt und Ansichten zuordnet, finden Sie unter Automatisches Erstellen eines Ansichtsmodells mit einem Ansichtsmodelllocator.

Hinweis

Das Registrieren und Auflösen von Typen mit einem Container wirkt sich negativ auf die Leistung aus, da der Container Reflektion zum Erstellen der einzelnen Typen verwendet, insbesondere dann, wenn Abhängigkeiten für jede Seitennavigation in der App rekonstruiert werden müssen. Wenn zahlreiche oder tiefe Abhängigkeiten vorhanden sind, können die Kosten für die Erstellung erheblich zunehmen.

Verwalten der Lebensdauer aufgelöster Objekte

Nach der Registrierung eines Typs mithilfe einer konkreten Klassenregistrierung besteht das Standardverhalten für TinyIoC darin, jedes Mal, wenn der Typ aufgelöst wird, oder wenn der Abhängigkeitsmechanismus Instanzen in andere Klassen einbindet, eine neue instance des registrierten Typs zu erstellen. In diesem Szenario enthält der Container keinen Verweis auf das aufgelöste Objekt. Wenn Sie jedoch einen Typ mithilfe der Schnittstellenregistrierung registrieren, besteht das Standardverhalten für TinyIoC darin, die Lebensdauer des Objekts als Singleton zu verwalten. Daher verbleibt der instance im Gültigkeitsbereich des Containers und wird gelöscht, wenn der Container aus dem Gültigkeitsbereich entfernt wird und garbage collection ist oder wenn der Code den Container explizit verwirrt.

Das Standardmäßige TinyIoC-Registrierungsverhalten kann mit den Methoden fluent AsSingleton und AsMultiInstance API überschrieben werden. Beispielsweise kann die AsSingleton -Methode mit der -Methode verwendet werden, sodass der Container beim Aufrufen Resolve der Register -Methode eine Singleton-instance eines Typs erstellt oder zurückgibt. Das folgende Codebeispiel zeigt, wie TinyIoC angewiesen wird, eine Singleton-instance der LoginViewModel -Klasse zu erstellen:

_container.Register<LoginViewModel>().AsSingleton();

Wenn der LoginViewModel Typ zum ersten Mal aufgelöst wird, erstellt der Container ein neues LoginViewModel Objekt und verwaltet einen Verweis darauf. Bei allen nachfolgenden Auflösungen von LoginViewModelgibt der Container einen Verweis auf das LoginViewModel zuvor erstellte Objekt zurück.

Hinweis

Typen, die als Singletons registriert sind, werden verworfen, wenn der Container verworfen wird.

Zusammenfassung

Die Abhängigkeitsinjektion ermöglicht die Entkopplung konkreter Typen vom Code, der von diesen Typen abhängt. Im Allgemeinen wird ein Container verwendet, der eine Liste von Registrierungen und Zuordnungen zwischen Schnittstellen und abstrakten Typen sowie die konkreten Typen enthält, die diese Typen implementieren oder erweitern.

TinyIoC ist ein einfacher Container, der im Vergleich zu den meisten bekannten Containern eine überlegene Leistung auf mobilen Plattformen bietet. Es erleichtert das Erstellen von lose gekoppelten Apps und bietet alle Features, die häufig in Abhängigkeitsinjektionscontainern zu finden sind, einschließlich Methoden zum Registrieren von Typzuordnungen, Auflösen von Objekten, Verwalten von Objektlebensdauern und Einfügen abhängiger Objekte in Konstruktoren von Objekten, die aufgelöst werden.