Dependency Injection ist eine Technik, bei der Sie die Interaktionen zwischen Objekten durch bestimmte Abhängigkeiten so gering wie möglich halten. Hier erfahren Sie, was Sie wissen müssen.
Dependency Injection (DI) ist eine Softwaretechnik, bei der die Interaktionen zwischen Objekten durch bestimmte Abhängigkeiten so dünn wie möglich gestaltet werden. Dadurch kann lose gekoppelter Code ausgeführt werden – oder Code, der nur vom erforderlichen Teil einer separaten Klasse abhängt. Außerdem werden dadurch hartcodierte Abhängigkeiten reduziert und saubererer, flexiblerer Code ermöglicht.
Um dieses Konzept zu veranschaulichen, nehmen wir an, Sie sind bereit, an der Supermarktkasse zu bezahlen. Geben Sie der Kassiererin Ihr Portemonnaie und lassen Sie sie nach dem Geld suchen, das Sie schulden? Natürlich nicht. Entweder geben Sie ihr Bargeld oder ziehen Ihre Kreditkarte durch. Sie halten die Schnittstelle zwischen Ihnen und dem Kassierer auf ein Minimum. Das ist die Essenz der dependency injection: Wenn Sie etwas in Ihrem Code benötigen, fordern Sie ihn auf, das zu tun und sonst nichts.
Bei der dependency injection geht es darum, Abhängigkeiten einzufügen, anstatt sie zu erstellen – ein einfaches, aber wirkungsvolles Konzept, das bessere Codierungspraktiken unterstützt. Es geht darum, gegen Abstraktionen zu codieren, nach dem zu fragen, was Sie brauchen, und lose gekoppelten Code zu akzeptieren. Obwohl es eine sorgfältige Planung und Gestaltung erfordert, zahlt sich der Aufwand aus, da die Codewartung in Sprachen wie Java , C#, C++ und PHP deutlich einfacher und effizienter wird.
Was ist dependency injection?
Eine Abhängigkeit ist alles, was eine bestimmte Klasse braucht, um ihre Aufgabe zu erfüllen, wie etwa Felder, andere Klassen usw. Wenn Klasse A zum Kompilieren die Anwesenheit von Klasse B benötigt, dann ist Klasse A von Klasse B abhängig. Mit anderen Worten: Klasse B ist eine Abhängigkeit von Klasse A. Abhängigkeiten werden erstellt, wenn Sie eine erstellen.
Hier ist ein Beispiel:
Im obigen Code haben wir eine fest codierte Abhängigkeit von ClassB in ClassA erstellt. Sie ist fest in die Klasse eingebunden. ClassA ist vollständig von ClassB abhängig. ClassB ist an ClassA gekoppelt. Eng gekoppelt . So eng gekoppelt, wie es nur geht. Und eine enge Kopplung ist schlecht.
Was ist Kopplung?
Kopplung ist die Vorstellung, dass eine Sache von einer anderen abhängig ist. Enge Kopplung bedeutet, dass Dinge wirklich voneinander abhängig und streng miteinander verbunden sind. Sie können ClassA nicht kompilieren, ohne dass die vollständige Definition von ClassB vorhanden ist. B ist dauerhaft an A gebunden.
Eine enge Kopplung ist schlecht, weil sie unflexiblen Code erzeugt. Überlegen Sie, wie schwierig es wäre, sich zu bewegen, wenn Sie an eine andere Person gefesselt wären. So fühlt sich eine Klasse an, wenn Sie fest codierte Abhängigkeiten erstellen.
Am besten halten Sie die Kopplung zwischen Ihren Klassen und Modulen so „locker“ wie möglich. Das heißt, Sie möchten, dass Ihre Abhängigkeiten so gering wie möglich sind. Wenn Sie eine Klasse haben, die einen Kundennamen benötigt, geben Sie nur den Kundennamen ein. Geben Sie nicht den gesamten Kundennamen ein. Und wie wir sehen werden, wenn Ihre Klasse eine andere Klasse benötigt, geben Sie eine Abstraktion ein, normalerweise eine Schnittstelle dieser Klasse. Eine Schnittstelle ist wie ein Rauchwölkchen. Da ist etwas, aber Sie können es nicht wirklich greifen.
So fügen Sie eine Abhängigkeit ein
Um eine Abhängigkeit einzufügen, erstellen Sie ClassB nicht innerhalb von ClassA. Fügen Sie die Abhängigkeit stattdessen über den Konstruktor ein:
An diesem Punkt haben wir zwei Arten der Kopplung: eine, bei der die erste Klasse eine Instanz der zweiten Klasse erstellt, und eine, bei der die erste Klasse die zweite Klasse benötigt (d. h. bei der sie injiziert wird). Letztere ist der ersteren vorzuziehen, da sie weniger (lockerere) Kopplung erfordert.
Dies ist die Essenz der dependency injection. Fügen Sie Ihre Abhängigkeiten ein, anstatt sie zu erstellen. Wenn Sie verstehen, dass Sie gegen Abstraktionen codieren und nach der Funktionalität fragen sollten, die Sie benötigen, sind Sie auf dem besten Weg, die dependency injection zu verstehen und besseren Code zu schreiben. Die dependency injection ist ein Mittel zum Zweck, und dieser Zweck ist lose gekoppelter Code.
Komponenten der dependency injection
Bei der dependency injection sind vier Klassentypen beteiligt, die jeweils bei der Ausführung der Technik eine Rolle spielen:
- Client: Der Client ist die Klasse, die eine Schnittstelle definiert und festlegt, welche Fähigkeiten oder Ressourcen sie benötigt. Daher erzeugen Clients keine Abhängigkeiten.
- Dienst: Der Dienst ist die Klasse, die die Schnittstelle mit den vom Client angeforderten spezifischen Fähigkeiten oder Ressourcen anwendet.
- Schnittstelle: Die Schnittstelle beschreibt die Fähigkeiten oder Ressourcen, die der Dienst bereitstellen muss. Schnittstellen ermöglichen lose Kopplung und flexiblen Code, da jeder Dienst, der die Schnittstelle anwendet, mit dem Client zusammenarbeiten kann.
- Injektor: Der Injektor ist die Klasse, die den Dienst in den Client einfügt. Er reguliert die Dienste und stellt sicher, dass die Clients zur richtigen Zeit die richtigen Fähigkeiten oder Ressourcen erhalten.
3 Arten der dependency injection
Es gibt drei gängige Typen der dependency injection, mit denen Sie vertraut sein sollten:
- Konstruktor-Injektion
- Eigenschaftseinfügung
- Injektionsmethode
1. Konstruktor-Injektion
Bei der Konstruktorinjektion handelt es sich um den Prozess, bei dem der Konstruktor verwendet wird, um die Abhängigkeiten einer Klasse zu übergeben. Die Abhängigkeiten werden als Parameter des Konstruktors deklariert. Daher können Sie keine neue Instanz der Klasse erstellen, ohne eine Variable des vom Konstruktor benötigten Typs zu übergeben.
Der letzte Punkt ist entscheidend. Wenn Sie eine Abhängigkeit als Parameter des Konstruktors deklarieren, sagen Sie damit: „Tut mir leid, Leute, aber wenn Sie diese Klasse erstellen möchten, müssen Sie diesen Parameter übergeben.“ Auf diese Weise kann eine Klasse die Abhängigkeiten angeben, die sie benötigt, und sicher sein, dass sie diese erhält. Sie können die Klasse nicht ohne sie erstellen. Wenn Sie diesen Code haben:
Sie können kein erstellen, PayrollSystemohne ihm eine Instanz von zu übergeben BankingService. Leider können Sie übergeben null, aber darauf kommen wir gleich zurück. PayrollSystemerklärt sehr deutlich, dass ein erforderlich ist BankingServiceund die Benutzer der Klasse eines bereitstellen müssen.
Akzeptieren Sie niemals Null
Wie ich bereits erwähnte, ist es bedauerlich, dass die obige Klasse null als Parameter annehmen kann und wird. Ich sage „annehmen“, weil ein Benutzer der Klasse zwar null übergeben kann , die Klasse selbst jedoch null nicht akzeptieren muss null.
Tatsächlich argumentiere ich, dass alle Methoden null, einschließlich Konstruktoren und regulärer Methoden, jederzeit explizit jeden Referenzparameter als Wert ablehnen sollten. Zu keinem Zeitpunkt sollte ein Parameter zulässig sein, nullohne dass die Methode eine Ausnahme auslöst. Wenn Sie nullan das PayrollSystemObige übergeben und die Klasse versucht, es zu verwenden, tritt ein Fehler auf. Und Fehler sind schlecht. Sie sollten – und können – vermieden werden.
Der obige Code sollte eigentlich ungefähr so aussehen:
Dieser Code lässt niemals zu, dass das interne Feld ist null. Es wird eine Ausnahme ausgelöst, wenn jemand es wagt, als Wert für den Parameter des Konstruktors zu übergeben null. So sollte es sein. Sie könnten akzeptieren null, aber dann müssten Sie überall in Ihrem Code danach suchen .
Die Überprüfung auf nullist Standardcode. Der Schutz vor nullder Übergabe als Parameter wird als Guard-Muster bezeichnet und Sie können Code schreiben, der vor nullder Übergabe an Ihre Methoden schützt. Hier ist eine grundlegende Implementierung einer Guard-Funktion:
Das Guard-Muster ist eigentlich als jeder Boolesche Ausdruck definiert, der als wahr ausgewertet werden muss, bevor die Programmausführung fortgesetzt werden kann. Es wird normalerweise verwendet, um sicherzustellen, dass bestimmte Voraussetzungen erfüllt sind, bevor eine Methode fortgesetzt werden kann, und um sicherzustellen, dass der folgende Code ordnungsgemäß ausgeführt werden kann. Die Überprüfung, ob eine Referenz nicht erfüllt ist, nullist wahrscheinlich die häufigste, aber nicht die einzige Verwendung des Guard-Musters.
Im vorliegenden Fall verwenden wir das Guard-Muster, um zu verhindern, dass ein Parameter null ist. Daher können wir das Guard-Muster verwenden, um unseren Code zu vereinfachen:
Wann wird Konstruktor-Injektion verwendet?
Sie sollten Konstruktor-Injektion verwenden, wenn Ihre Klasse eine Abhängigkeit hat, die die Klasse benötigt, um richtig zu funktionieren. Wenn Ihre Klasse nicht ohne eine Abhängigkeit funktionieren kann, injizieren Sie sie über den Konstruktor. Wenn Ihre Klasse drei Abhängigkeiten benötigt, fordern Sie alle drei im Konstruktor an.
Darüber hinaus sollten Sie Konstruktorinjektion verwenden, wenn die betreffende Abhängigkeit eine Lebensdauer hat, die länger als eine einzelne Methode ist. An den Konstruktor übergebene Abhängigkeiten sollten für die Klasse allgemein nützlich sein und ihre Verwendung mehrere Methoden in der Klasse umfassen. Wenn eine Abhängigkeit nur an einer Stelle verwendet wird, ist Methodeninjektion (siehe unten) möglicherweise die bessere Wahl.
Die Konstruktorinjektion sollte die Hauptmethode für die dependency injection sein. Es ist ganz einfach: Eine Klasse benötigt etwas und fordert es daher an, bevor sie überhaupt erstellt werden kann. Durch die Verwendung des Guard-Musters können Sie die Klasse vertrauensvoll verwenden, da Sie wissen, dass die Feldvariable, die diese Abhängigkeit speichert, eine gültige Instanz ist. Außerdem ist es wirklich einfach und klar durchzuführen.
Die Konstruktorinjektion sollte Ihre bevorzugte Dependency-Injection-Technik für klaren, entkoppelten Code sein. Aber sie sollte nicht das einzige Tool in Ihrem Werkzeugkasten sein.
2. Eigenschaftseinfügung
Manchmal hat eine Klasse eine Abhängigkeit, die nicht unbedingt erforderlich ist, aber von der Klasse tatsächlich verwendet wird. Ein Beispiel könnte eine Dokumentklasse sein, in der möglicherweise eine Grammatikprüfung installiert ist, aber nicht muss. Wenn eine vorhanden ist, ist das großartig, denn die Klasse kann sie verwenden. Wenn keine vorhanden ist, ist das großartig, denn die Klasse kann eine Standardimplementierung als Platzhalter einschließen.
Die Lösung hier ist die Eigenschaftsinjektion (auch Setter-Injektion genannt). Sie fügen Ihrer Klasse eine Eigenschaft hinzu, die auf eine gültige Instanz der betreffenden Klasse gesetzt werden kann. Da die Abhängigkeit eine Eigenschaft ist, können Sie sie nach Wunsch setzen. Wenn die Abhängigkeit nicht gewünscht oder benötigt wird, können Sie die Eigenschaft so lassen, wie sie ist.
Ihr Code sollte sich so verhalten, als ob die Abhängigkeit vorhanden wäre. Sie sollten also eine Standardimplementierung bereitstellen, die nichts tut, damit der Code mit oder ohne echte Abhängigkeit ausgeführt werden kann. Denken Sie daran, dass wir nie wollen, dass etwas null ist. Daher sollte die Standardimplementierung gültig sein. Wenn der Benutzer der Klasse eine funktionierende Implementierung bereitstellen möchte, kann er das tun. Wenn nicht, gibt es einen funktionierenden Standard, der es der enthaltenen Klasse ermöglicht, weiterhin zu funktionieren.
Wann wird die Eigenschafteninjektion verwendet?
Verwenden Sie die Eigenschafteninjektion, wenn eine Abhängigkeit optional ist und/oder wenn eine Abhängigkeit nach der Instanziierung der Klasse geändert werden kann. Verwenden Sie sie, wenn Sie möchten, dass Benutzer der enthaltenen Klasse ihre eigene Implementierung der betreffenden Schnittstelle bereitstellen können. Sie sollten die Eigenschafteninjektion nur verwenden, wenn Sie eine Standardimplementierung der betreffenden Schnittstelle bereitstellen können.
Jede Standardimplementierung ist wahrscheinlich eine nicht funktionsfähige Implementierung. Das muss aber nicht sein. Wenn Sie eine funktionierende Standardimplementierung bereitstellen möchten, ist das in Ordnung. Beachten Sie jedoch, dass Sie sich durch die Verwendung der Eigenschafteninjektion und das Erstellen dieser Klasse im Konstruktor des enthaltenden Objekts an diese Implementierung binden.
Beispiel für die Eigenschafteninjektion
Sehen wir uns Code an, der das macht, was ich oben beschrieben habe – eine Dokumentklasse mit einer optionalen Grammatikprüfung.
Zunächst beginnen wir mit einer Schnittstelle:
Jetzt implementieren wir es zweimal: einmal als Nichtstun-Standard und erneut als echte Grammatikprüfung.
Beide Implementierungen melden sich einfach in der Konsole an, sogar die nicht-operative Implementierung. Ich wollte lediglich sicherstellen, dass alles richtig funktioniert. Auch hier defaultGrammarCheckerhandelt es sich um eine nicht-operative Standardimplementierung, die uns davon abhält, ständig nachsehen zu müssen null.
Jetzt benötigen wir eine Klasse mit einer Eigenschaft für die Grammatikprüfung.
Zu diesem Code sind folgende Dinge zu beachten:
- Sein Konstruktor verwendet den Dokumenttext als Parameter. Dieser wird dann als Lese-/Schreibeigenschaft angezeigt, sodass Sie bei Bedarf darauf zugreifen und ihn ändern können.
- Der Konstruktor erstellt auch eine Instanz des Standardgrammatikprüfers. Beachten Sie erneut, dass dadurch eine fest codierte Abhängigkeit erstellt wird – eine der Gefahren der Eigenschaftseinfügung. Die Abhängigkeit ist jedoch standardmäßig nichts und verhindert, dass wir ständig auf Null prüfen müssen.
- Der Setter für die grammarCheckerEigenschaft enthält einen Guard-Call, der sicherstellt, dass der interne Wert _grammarCheckerniemals null sein kann.
- Beachten Sie außerdem, dass die grammarCheckerEigenschaft eine schreibgeschützte Eigenschaft ist. Das heißt, Sie können den Wert nur festlegen und nie extern lesen. Sie können eine schreibgeschützte Eigenschaft erstellen, wenn der geschriebene Wert nur intern verwendet wird und nie von Code außerhalb der Klasse aufgerufen wird.
Hier ist etwas Code, der alles übt und die Eigenschaftsinjektion in Aktion zeigt:
Zum obigen Code ist Folgendes zu beachten:
- Es erstellt ein Dokument und verwendet einen Text als Konstruktorparameter.
- Es ruft CheckGrammar auf, aber der Standard-Grammatikprüfer tut nichts, daher wird dies in der Konsole angezeigt.
- Aber dann verwenden wir die Eigenschafteninjektion, um einen echten Grammatikprüfer einzufügen, und wenn wir CheckGrammar aufrufen, wird die Grammatik wirklich geprüft.
Mithilfe der Eigenschaftseinfügung können Sie optionale Abhängigkeiten bereitstellen. Sie können bei Bedarf auch eine Abhängigkeit ändern. Ihre Dokumentklasse kann beispielsweise Texte aus verschiedenen Sprachen annehmen und erfordern, dass sich die Grammatikprüfung an die Sprache des Dokuments anpasst. Mithilfe der Eigenschaftseinfügung ist dies möglich.
3. Methode Injektion
Was passiert, wenn die Abhängigkeit, die Ihre Klasse benötigt, häufig unterschiedlich ist? Was passiert, wenn die Abhängigkeit eine Schnittstelle ist und Sie mehrere Implementierungen haben, die Sie möglicherweise an die Klasse übergeben möchten? Sie könnten die Eigenschaftseinfügung verwenden, aber dann müssten Sie die Eigenschaft jedes Mal festlegen, bevor Sie die Methode aufrufen, die die sich häufig ändernde Abhängigkeit verwendet, wodurch die Möglichkeit einer zeitlichen Kopplung entsteht.
Zeitliche Kopplung bedeutet, dass die Ausführungsreihenfolge in einer bestimmten Weise erfolgen muss, damit die Dinge richtig funktionieren.
Konstruktor- und Eigenschaftsinjektion werden im Allgemeinen verwendet, wenn Sie eine Abhängigkeit haben, die sich nicht oft ändert. Sie sind daher nicht geeignet, wenn Ihre Abhängigkeit eine von vielen Implementierungen sein kann.
Hier kommt die dritte Art der dependency injection ins Spiel – die Methodeninjektion.
Mithilfe der Methodeninjektion können Sie eine Abhängigkeit direkt am Verwendungsort einfügen, sodass Sie jede gewünschte Implementierung übergeben können, ohne sich Gedanken über die Speicherung für die spätere Verwendung machen zu müssen. Dies wird häufig verwendet, wenn Sie andere Informationen übergeben, die eine besondere Behandlung erfordern. Beispiel:
Hier haben wir das Konzept eines Rezepts, das je nach Rezept einen anderen Zubereiter erfordern kann. Nur die aufrufende Entität weiß, welcher Zubereitertyp für ein bestimmtes Rezept der richtige ist. Beispielsweise könnte ein Rezept einen Schnellkoch erfordern und ein anderes Rezept einen Bäcker oder Koch. Beim Schreiben des Codes wissen wir nicht , welcher Typ IFoodPreparerbenötigt wird, daher können wir die Abhängigkeit nicht einfach im Konstruktor übergeben und dann bei dieser einen Implementierung bleiben.
Außerdem ist es umständlich, jedes Mal eine Eigenschaft festzulegen, wenn eine neue oder andere IFoodPreparererforderlich ist. Und das Festlegen der Eigenschaft auf diese Weise führt zu einer zeitlichen Kopplung und führt zu Thread-Sicherheitsproblemen, da in einer Thread-Umgebung eine Sperre für den Code erforderlich wäre.
Die beste Lösung besteht darin, die Abhängigkeit einfach IFoodPrepareram Verwendungsort in die Methode zu übergeben.
Methodeninjektion sollte verwendet werden, wenn sich die Abhängigkeit bei jeder Verwendung ändern könnte oder zumindest, wenn Sie nicht sicher sind, welche Abhängigkeit am Verwendungsort benötigt wird.
Hier ist ein Beispiel für die Verwendung von Methodeninjektion, wenn die Abhängigkeit bei jeder Verwendung geändert werden muss. Stellen Sie sich eine Situation vor, in der ein Autolackierroboter nach jedem lackierten Auto eine neue Lackierpistolenspitze benötigt. Sie könnten wie folgt beginnen und Konstruktorinjektion verwenden:
Wenn wir das Auto lackieren, müssen wir eine neue Lackierpistolenspitze besorgen. Aber wie? Wenn wir das Auto lackieren, ist die Spitze nicht mehr gut, aber es ist eine Schnittstelle und wir haben keine Möglichkeit, sie manuell freizugeben. Selbst wenn wir das täten, was würden wir das nächste Mal tun, wenn wir ein Auto lackieren müssen? Wir wissen nicht, welche Art von Spitze für ein bestimmtes Auto benötigt wird, und wenn wir unsere Belange richtig getrennt haben, wissen wir nicht einmal, wie man eine neue Spitze erstellt. Was tun? Verwenden Sie stattdessen Methodeninjektion:
Wenn Sie eine Methode mithilfe der Methodeninjektion implementieren, müssen Sie eine Schutzklausel einschließen. Die Abhängigkeit wird sofort verwendet, und wenn Sie versuchen, sie zu verwenden, wenn sie null ist, erhalten Sie sofort einen Fehler. Dies sollte offensichtlich vermieden werden.
Wenn wir nun die Abhängigkeit direkt an die Methode übergeben, verlässt die Schnittstelle den Gültigkeitsbereich, wenn wir mit dem Lackieren fertig sind und die Lackierpistolenspitze zerstört wird. Wenn ein Auto das nächste Mal lackiert werden muss, übergibt der Verbraucher außerdem eine neue Spitze, die bei der Verwendung freigegeben wird.
Wann wird die Methodeninjektion verwendet?
Die Methodeninjektion ist in zwei Szenarien nützlich:
- Der Zeitpunkt der Implementierung einer Abhängigkeit kann unterschiedlich sein.
- Wenn die Abhängigkeit nach jedem Gebrauch erneuert werden muss.
In beiden Fällen muss der Anrufer entscheiden, welche Implementierung an die Methode übergeben wird.
5 Prinzipien der dependency injection, die Sie befolgen sollten
Es gibt fünf Grundprinzipien, die Sie bei der dependency injection befolgen sollten:
1. Code gegen Abstraktionen, nicht gegen Implementierungen
Erich Gamma von der „Gang of Four“ , den Autoren des Buches „ Design Patterns“ , gilt als Urheber des Satzes „Code gegen Abstraktionen, nicht gegen Implementierungen“. Und das ist eine wirkungsvolle Idee.
Abstraktionen, normalerweise, aber nicht immer, Schnittstellen, sind flexibel und können auf viele Arten implementiert werden. Das Codieren auf der Grundlage von Abstraktionen statt Implementierungen kann eine enge Kopplung verhindern und zu einem vielseitigeren System führen. Vermeiden Sie die Festlegung auf eine einzige Implementierung; verwenden Sie Abstraktionen für einen besser wiederverwendbaren und anpassbaren Code.
2. Erschaffe niemals Dinge, die nicht erschaffen werden sollten
Bei der dependency injection sollten Ihre Klassen dem Single-Responsibility-Prinzip folgen , also der Idee, dass eine Klasse nur eine Sache tun sollte. Wenn sie das tun, sollten sie keine Dinge erstellen, denn das würde bedeuten, dass sie zwei Dinge tun. Stattdessen sollten sie nach der Funktionalität fragen, die sie brauchen, und diese Funktionalität von etwas anderem erstellen und bereitstellen lassen.
Es gibt zwei verschiedene Arten von Objekten, deren Erstellung wir in Betracht ziehen sollten: erstellbare und injizierbare Objekte.
Kreierbare Stoffe vs. injizierbare Stoffe
Creatables sind Klassen, die Sie erstellen sollten. Im Allgemeinen sollten Klassen in der Laufzeitbibliothek als Creatables betrachtet werden. Sie haben normalerweise eine kurze Lebensdauer und leben nicht länger als die Spanne einer einzelnen Methode. Wenn sie von der Klasse als Ganzes benötigt werden, können sie im Konstruktor erstellt werden. Nur andere Creatables sollten an den Konstruktor eines Creatables übergeben werden.
Injectables hingegen sind Klassen, die wir niemals direkt erstellen oder für die wir eine Abhängigkeit fest codieren möchten. Stattdessen sollten sie immer per dependency injection übergeben werden. Sie werden normalerweise als Abhängigkeiten in einem Konstruktor angefordert und sollten über Schnittstellen referenziert werden – nicht über direkte Verweise auf eine Instanz.
Injectables sind meistens Klassen, die Sie als Teil Ihrer Geschäftslogik schreiben . Sie sollten immer hinter einer Abstraktion, normalerweise einer Schnittstelle, verborgen sein. Beachten Sie auch, dass Injectables in ihrem Konstruktor andere Injectables anfordern können.
3. Halten Sie Konstruktoren einfach
Der Konstruktor einer Klasse sollte keine Arbeit verrichten. Er sollte nichts anderes tun, als nach zu suchen null, creatables zu erstellen und Abhängigkeiten für die spätere Verwendung zu speichern. Er sollte keine Kodierungslogik enthalten. Eine ifKlausel im Konstruktor einer Klasse, die nicht nach sucht, nullist ein Schrei danach, diese Klasse in zwei Klassen aufzuteilen. Es gibt Möglichkeiten, nach Nullwertparametern zu suchen, die keine Anweisung beinhalten if.
Ein komplexer Konstruktor ist ein klares Zeichen dafür, dass Ihre Klasse zu viel tut. Halten Sie Konstruktoren kurz, einfach und frei von jeglicher Logik während der dependency injection.
4. Gehen Sie bei der Umsetzung nicht von Annahmen aus
Schnittstellen sind ohne Implementierung nutzlos. Aber stellen Sie niemals Annahmen darüber an, wie diese Implementierung aussieht.
Sie sollten nur auf Grundlage des von der Schnittstelle festgelegten Vertrags codieren. Sie haben möglicherweise die Implementierung geschrieben, aber Sie sollten nicht auf Grundlage der Schnittstelle codieren und dabei diese Implementierung im Hinterkopf behalten. Mit anderen Worten: Codieren Sie auf Grundlage Ihrer Schnittstelle, als stünde eine radikal neue und bessere Implementierung dieser Schnittstelle unmittelbar bevor.
Eine gut gestaltete Schnittstelle sagt Ihnen, was Sie tun müssen und wie Sie es verwenden. Die Implementierung dieser Schnittstelle sollte für Ihre Verwendung der Schnittstelle unerheblich sein.
5. Gehen Sie nicht davon aus, dass eine Schnittstelle eine Abstraktion ist
Nicht jede Schnittstelle ist eine Abstraktion. Wenn Ihre Schnittstelle beispielsweise eine exakte Darstellung des öffentlichen Teils Ihrer Klasse ist, abstrahieren Sie eigentlich nichts. Solche Schnittstellen werden Header-Schnittstellen genannt, da sie C++- Header-Dateien ähneln. Aus Klassen extrahierte Schnittstellen können leicht eng an diese Klasse allein gekoppelt werden, wodurch die Schnittstelle als Abstraktion nutzlos wird confusion matrix.
Schließlich können Abstraktionen undicht sein. Sie können bestimmte Implementierungsdetails über ihre Implementierung preisgeben. Undichtige Abstraktionen sind normalerweise auch an eine bestimmte Implementierung gebunden.
Vorteile der dependency injection
Warum sollten wir uns die Mühe machen, unseren Code so anzuordnen, wie es die Prinzipien der dependency injection erfordern? Weil die dependency injection viele Vorteile bietet.
1. Code mit Dependency Injection ist wartbar
Einer der Hauptvorteile der dependency injection ist die Wartbarkeit. Wenn Ihre Klassen lose gekoppelt sind und dem Single-Responsibility-Prinzip folgen, ist Ihr Code einfacher zu warten.
Einfache, eigenständige Klassen lassen sich leichter reparieren als komplizierte, eng gekoppelte Klassen.
Wartbarer Code hat geringere Gesamtbetriebskosten. Die Wartungskosten übersteigen oft die Kosten für die Erstellung des Codes. Alles, was die Wartbarkeit Ihres Codes verbessert, ist also eine gute Sache.
2. Code mit Dependency Injection ist testbar
Code, der einfach zu testen ist, wird häufiger getestet. Mehr Tests bedeuten höhere Qualität.
Lose gekoppelte Klassen, die nur eine Sache tun, können sehr einfach Unit-Tests unterzogen werden . Durch die Verwendung der dependency injection wird das Erstellen von Testdoubles, allgemein als „ Mocks “ bezeichnet, viel einfacher.
Wenn Sie Abhängigkeiten an Klassen übergeben, können Sie problemlos eine Test-Double-Implementierung übergeben. Wenn Abhängigkeiten fest codiert sind, ist es unmöglich, Test-Doubles für diese Abhängigkeiten zu erstellen.
Testbarer Code, der tatsächlich getestet wird, ist Qualitätscode. Oder zumindest ist er von höherer Qualität als ungetesteter Code.
3. Code mit Dependency Injection ist lesbar
Code, der Dependency Injection verwendet, ist unkomplizierter. Er folgt dem Single-Responsibility-Prinzip, was zu kleineren, kompakteren und auf den Punkt gebrachten Klassen führt.
Konstruktoren sind nicht so überladen und mit Logik vollgestopft. Klassen sind klarer definiert und geben offen an, was sie brauchen. Aus all diesen Gründen ist auf dependency injection basierender Code besser lesbar. Und besser lesbarer Code ist besser wartbar.
4. Code mit Dependency Injection ist flexibel
Lose gekoppelter Code ist flexibler und kann auf verschiedene Arten verwendet werden. Kleine Klassen, die eine Aufgabe erfüllen, können leichter neu zusammengesetzt und in verschiedenen Situationen wiederverwendet werden.
Kleine Klassen sind wie Legosteine – sie können leicht zusammengesetzt werden, um eine Vielzahl von Dingen zu bauen, im Gegensatz zu Duplosteinen, die sperriger und weniger flexibel sind. Die Möglichkeit, Code wiederzuverwenden, spart Zeit und Geld.
Jede Software muss sich ändern und an neue Anforderungen anpassen können. Lose gekoppelter Code, der dependency injection verwendet, ist flexibel und kann sich an diese Änderungen anpassen.
5. Code mit Dependency Injection ist erweiterbar
Code, der Abhängigkeitseinspritzung verwendet, führt zu einer erweiterbareren Klassenstruktur. Durch die Verwendung von Abstraktionen statt Implementierungen kann der Code eine gegebene Implementierung leicht variieren.
Wenn Sie auf der Grundlage von Abstraktionen codieren, können Sie mit der Annahme codieren, dass eine radikal bessere Implementierung dessen, was Sie tun, unmittelbar bevorsteht.
Kleine, flexible Klassen können einfach erweitert werden, entweder durch Vererbung oder Zusammensetzung.
Die Codebasis einer Anwendung bleibt nie statisch und Sie werden höchstwahrscheinlich neue Funktionen hinzufügen müssen, wenn Ihre Codebasis wächst und neue Anforderungen entstehen. Erweiterbarer Code ist dieser Herausforderung gewachsen.
6. Code mit dependency injection ist teilbar
Wenn Sie an einem Teamprojekt arbeiten, erleichtert die dependency injection die Teamentwicklung. Selbst wenn Sie alleine arbeiten, wird Ihre Arbeit wahrscheinlich in Zukunft an jemanden weitergegeben .
Bei der dependency injection müssen Sie auf der Grundlage von Abstraktionen und nicht von Implementierungen codieren.
Wenn zwei Teams zusammenarbeiten und jedes die Arbeit des anderen benötigt, können Sie die Abstraktionen definieren, bevor Sie die Implementierungen durchführen. Dann kann jedes Team seinen Code unter Verwendung der Abstraktionen schreiben, noch bevor die Implementierungen geschrieben sind.
Da der Code lose gekoppelt ist, sind diese Implementierungen außerdem nicht voneinander abhängig und können daher leichter zwischen den Teams aufgeteilt werden.
Häufig gestellte Fragen
Dependency Injection ist eine Programmiertechnik, bei der eine Softwarekomponente, ein sogenanntes „Objekt“, eingefügt wird, anstatt die Komponente zu erstellen. Das Ergebnis ist ein weitgehend unabhängiger Code, der nur von bestimmten Komponenten abhängig ist. So können Entwickler mit saubererem und flexiblerem Code arbeiten.
Die drei Arten der dependency injection sind Konstruktorinjektion, Eigenschaftsinjektion und Methodeninjektion.