Ich habe kürzlich an einem Python-Projekt gearbeitet, in dem wir eine starke Abhängigkeitsinjektion durchgeführt haben (weil wir dies tun müssen, damit die App testbar ist), aber wir haben kein Framework verwendet. Manchmal war es etwas mühsam, alle Abhängigkeiten manuell zu verbinden, aber insgesamt hat es super funktioniert.
Wenn ein Objekt an mehreren Stellen erstellt werden musste, hatten wir einfach eine Funktion (manchmal eine Klassenmethode dieses Objekts), um eine Produktionsinstanz zu erstellen. Diese Funktion wurde aufgerufen, wann immer wir das Objekt brauchten.
In den Tests haben wir dasselbe getan: Wenn wir mehrere Male eine 'Testversion' eines Objekts (dh eine von Scheinobjekten gesicherte reale Instanz) erstellen mussten, hatten wir eine Funktion, um diese 'Testversion' zu erstellen. einer Klasse, die in Tests verwendet werden soll.
Wie gesagt, im Allgemeinen hat das gut funktioniert.
Ich betrete jetzt ein neues Java-Projekt und wir entscheiden, ob wir ein DI-Framework verwenden oder DI manuell ausführen (wie im vorherigen Projekt).
Bitte erläutern Sie, wie sich die Arbeit mit einem DI-Framework unterscheidet und wie sie besser oder schlechter ist als die manuelle 'Vanille'-Abhängigkeitsinjektion.
Bearbeiten: Ich möchte auch zu der Frage hinzufügen: Haben Sie ein professionelles Projekt erlebt, das DI manuell ohne Rahmen ausführt?
quelle
Antworten:
Der Schlüssel von DI-Containern ist die Abstraktion . DI-Behälter abstrahieren dieses Designanliegen für Sie. Wie Sie sich vorstellen können, hat dies spürbare Auswirkungen auf den Code, der häufig in weniger Konstrukteure, Setter, Fabriken und Builder übersetzt wird. Infolgedessen wird Ihr Code (aufgrund der zugrunde liegenden IoC ) lockerer , sauberer und einfacher gekoppelt . Obwohl nicht ohne Kosten.
Hintergründe
Vor Jahren mussten wir für eine solche Abstraktion verschiedene Konfigurationsdateien schreiben ( deklarative Programmierung ). Es war fantastisch zu sehen, wie sich die Anzahl der LOCs erheblich verringerte, aber während die LOCSs abnahmen, stieg die Anzahl der Konfigurationsdateien leicht an. Für mittlere oder kleine Projekte war es überhaupt kein Problem, aber für größere Projekte wurde das Problem, dass die "Assemblierung des Codes" zu 50% zwischen Code- und XML-Deskriptoren verstreut war, zu einem ... Das Hauptproblem war jedoch das Paradigma selbst. Es war ziemlich unflexibel, weil die Deskriptoren wenig Platz für Anpassungen ließen.
Das Paradigma verlagerte sich zunehmend in Richtung Konvention über Konfiguration , bei der Konfigurationsdateien gegen Anmerkungen oder Attribute ausgetauscht werden. Es hält unseren Code sauberer und einfacher und bietet uns gleichzeitig die Flexibilität, die XML nicht bieten konnte.
Dies ist wahrscheinlich der bedeutendste Unterschied in Bezug auf Ihre bisherige Arbeitsweise. Weniger Code und mehr Magie.
Auf der Unterseite...
Konvention über Konfiguration macht die Abstraktion so schwer, dass Sie kaum wissen, wie es funktioniert. Manchmal magisch und hier kommt noch ein Nachteil. Sobald es funktioniert, kümmern sich viele Entwickler nicht mehr darum, wie es funktioniert.
Dies ist ein Handicap für diejenigen, die DI mit DI-Containern gelernt haben. Sie verstehen die Relevanz der Entwurfsmuster, der bewährten Verfahren und der Prinzipien, die vom Container abstrahiert wurden, nicht vollständig. Ich habe Entwickler gefragt, ob sie mit DI und seinen Vorteilen vertraut sind, und sie haben geantwortet: - Ja, ich habe Spring IoC verwendet -. (Was zur Hölle heißt das?!?)
Man kann jedem dieser "Nachteile" zustimmen oder nicht. Meiner bescheidenen Meinung nach ist es ein Muss zu wissen, was in Ihrem Projekt vor sich geht. Ansonsten haben wir kein vollständiges Verständnis davon. Es ist auch von Vorteil, zu wissen, wofür DI ist, und eine Vorstellung davon zu haben, wie es implementiert werden soll.
Auf der positiven Seite...
Ein Wort Produktivität . Mit Frameworks können wir uns auf das konzentrieren, was wirklich wichtig ist. Das Geschäft der Anwendung.
Trotz der bemerkten Mängel sind diese Tools für viele von uns (welcher Job durch Termine und Kosten bedingt ist) eine unschätzbare Ressource. Meiner Meinung nach sollte dies unser Hauptargument für die Implementierung eines DI-Containers sein. Produktivität .
Testen
Unabhängig davon, ob wir reine DI oder Container implementieren, ist das Testen kein Problem. Ganz im Gegenteil. Die gleichen Behälter liefern uns oft die Mocks für Unit-Tests aus der Box. Im Laufe der Jahre habe ich meine Tests als Thermometer verwendet. Sie sagen mir, ob ich mich stark auf die Containereinrichtungen verlassen habe. Ich sage das, weil diese Container private Attribute ohne Setter oder Konstruktoren initialisieren können. Sie können Bauteile fast überall einspritzen!
Klingt verlockend, oder? Achtung! Fallen Sie nicht in die Falle !!
Vorschläge
Wenn Sie sich für die Implementierung von Containern entscheiden, empfehle ich dringend, sich an die bewährten Methoden zu halten. Implementieren Sie weiterhin Konstruktoren, Setter und Interfaces. Erzwingen Sie die Verwendung des Frameworks / Containers . Dies erleichtert die Migration in ein anderes Framework oder das Entfernen des eigentlichen Frameworks. Dies kann die Abhängigkeit vom Container erheblich verringern.
In Bezug auf diese Praktiken:
Wann sind DI-Container zu verwenden?
Meine Antwort wird durch die eigenen Erfahrungen verzerrt sein, die hauptsächlich für DI-Container sprechen (wie ich bemerkte, konzentriere ich mich stark auf Produktivität, so dass ich nie die Notwendigkeit einer reinen DI hatte. Ganz im Gegenteil).
Ich denke, Sie werden einen interessanten Artikel von Mark Seeman zu diesem Thema finden, der auch die Frage beantwortet, wie man reine DI implementiert .
Wenn wir schließlich von Java sprechen, würde ich zunächst einen Blick auf JSR330 - Dependency Injection werfen .
Zusammenfassend
Vorteile :
Nachteile :
Unterschiede zu reinem DI :
quelle
Nach meiner Erfahrung führt ein Abhängigkeitsinjektionsframework zu einer Reihe von Problemen. Einige der Probleme hängen vom Framework und den Tools ab. Möglicherweise gibt es Vorgehensweisen, die die Probleme lindern. Aber das sind die Probleme, die ich getroffen habe.
Nicht globale Objekte
In einigen Fällen möchten Sie ein globales Objekt einfügen: Ein Objekt, das nur eine Instanz in Ihrer ausgeführten Anwendung enthält. Dies könnte eine sein
MarkdownToHtmlRendererer
, aDatabaseConnectionPool
, die Adresse für Ihren API-Server usw. sein. In diesen Fällen funktionieren Abhängigkeitsinjektions-Frameworks sehr einfach. Fügen Sie einfach die erforderliche Abhängigkeit zu Ihrem Konstruktor hinzu, und Sie haben sie.Aber was ist mit Objekten, die nicht global sind? Was ist, wenn Sie den Benutzer für die aktuelle Anforderung benötigen? Was ist, wenn Sie die aktuell aktive Datenbanktransaktion benötigen? Was ist, wenn Sie das HTML-Element für das div benötigen, das eine Form enthält, in der sich ein Textfeld befindet, das gerade ein Ereignis ausgelöst hat?
Die Lösung, die ich bei DI-Frameworks gesehen habe, sind hierarchische Injektoren. Dies macht das Einspritzmodell jedoch komplizierter. Es wird schwieriger, herauszufinden, was aus welcher Hierarchieebene injiziert wird. Es wird schwierig zu sagen, ob ein bestimmtes Objekt pro Anwendung, pro Benutzer, pro Anforderung usw. vorhanden ist. Sie können nicht mehr nur Ihre Abhängigkeiten hinzufügen und es funktionieren lassen.
Bibliotheken
Oft möchten Sie eine Bibliothek mit wiederverwendbarem Code haben. Dies enthält auch Anweisungen zum Erstellen von Objekten aus diesem Code. Wenn Sie ein Abhängigkeitsinjektionsframework verwenden, enthält dieses normalerweise Bindungsregeln. Wenn Sie die Bibliothek verwenden möchten, fügen Sie deren Bindungsregeln zu Ihren hinzu.
Es können sich jedoch verschiedene Probleme ergeben. Möglicherweise wird dieselbe Klasse an verschiedene Implementierungen gebunden. Die Bibliothek erwartet möglicherweise bestimmte Bindungen. Die Bibliothek überschreibt möglicherweise einige Standardbindungen, was dazu führt, dass Ihr Code seltsame Dinge tut. Ihr Code überschreibt möglicherweise eine Standardbindung, wodurch der Bibliothekscode seltsame Dinge tut.
Versteckte Komplexität
Wenn Sie Fabriken manuell schreiben, haben Sie ein Gefühl dafür, wie die Abhängigkeiten der Anwendung zusammenpassen. Wenn Sie ein Abhängigkeitsinjektionsframework verwenden, wird dies nicht angezeigt. Stattdessen neigen Objekte dazu, immer mehr Abhängigkeiten zu entwickeln, bis früher oder später alles von so ziemlich allem abhängt.
IDE-Unterstützung
Ihre IDE wird das Framework für die Abhängigkeitsinjektion wahrscheinlich nicht verstehen. In manuellen Fabriken finden Sie alle Aufrufer eines Konstruktors, um alle Stellen herauszufinden, an denen das Objekt möglicherweise erstellt wird. Die vom DI-Framework generierten Aufrufe werden jedoch nicht gefunden.
Fazit
Was erhalten wir von einem Abhängigkeitsinjektionsframework? Wir müssen ein einfaches Boilerplate vermeiden, das zum Instanziieren von Objekten benötigt wird. Ich bin alles dafür, einfältige Kesselplatten zu eliminieren. Es ist jedoch einfach, eine einfache Kesselplatte zu warten. Wenn wir diese Heizplatte ersetzen wollen, sollten wir darauf bestehen, sie durch etwas einfacheres zu ersetzen. Aufgrund der verschiedenen Probleme, die ich oben besprochen habe, glaube ich nicht, dass Frameworks (zumindest so wie sie sind) der Rechnung entsprechen.
quelle
Ist Java + Dependency Injection = Framework erforderlich?
Nein.
Was bringt mir ein DI-Framework?
Die Objektkonstruktion erfolgt in einer Sprache, die nicht Java ist.
Wenn die Factory-Methoden für Ihre Anforderungen gut genug sind, können Sie DI in Java vollständig ohne Framework in 100% authentischem Pojo Java ausführen. Wenn Ihre Bedürfnisse darüber hinausgehen, haben Sie andere Möglichkeiten.
Die Designmuster der Gang of Four bringen viele großartige Dinge zustande, hatten aber immer Schwächen, wenn es um die Kreationsmuster ging. Java selbst hat hier einige Schwächen. DI-Frameworks helfen bei dieser kreativen Schwäche mehr als bei den eigentlichen Injektionen. Sie wissen schon, wie man das ohne sie macht. Wie viel Gutes sie tun, hängt davon ab, wie viel Hilfe Sie bei der Objektkonstruktion benötigen.
Es gibt viele Schöpfungsmuster, die nach der Viererbande herauskamen. Der Josh Bloch Builder ist ein wunderbarer Hack um Javas Mangel an benannten Parametern. Darüber hinaus gibt es iDSL-Builder, die viel Flexibilität bieten. Aber jedes von diesen stellt bedeutende Arbeit und Boilerplate dar.
Frameworks können Sie vor etwas Langeweile bewahren. Sie sind nicht ohne Nachteile. Wenn Sie nicht aufpassen, sind Sie möglicherweise auf das Framework angewiesen. Versuchen Sie, das Framework nach der Verwendung zu wechseln, und überzeugen Sie sich selbst. Selbst wenn Sie die generische XML- oder Annotations-Version beibehalten, werden möglicherweise zwei Sprachen unterstützt, die Sie bei der Bewerbung von Programmieraufträgen nebeneinander erwähnen müssen.
Bevor Sie sich zu sehr in die Leistungsfähigkeit von DI-Frameworks verlieben, nehmen Sie sich etwas Zeit und untersuchen Sie andere Frameworks, die Ihnen Funktionen bieten, für die Sie möglicherweise andernfalls ein DI-Framework benötigen. Viele haben sich über die einfache DI hinaus verzweigt . Bedenken Sie: Lombok , AspectJ und slf4J . Jedes überlappt sich mit einigen DI-Frameworks, aber jedes funktioniert für sich.
Wenn das Testen wirklich die treibende Kraft dahinter ist, schauen Sie sich Folgendes an : jUnit (jedes xUnit wirklich) und Mockito (jedes Mock wirklich), bevor Sie überlegen, ob ein DI-Framework Ihr Leben übernehmen sollte.
Sie können tun, was Sie wollen, aber für ernsthafte Arbeiten bevorzuge ich einen gut gefüllten Werkzeugkasten einem Schweizer Taschenmesser. Ich wünschte, es wäre einfacher, diese Linien zu zeichnen, aber einige haben hart gearbeitet, um sie zu verwischen. Daran ist nichts auszusetzen, aber es kann diese Entscheidungen schwierig machen.
quelle
Das Erstellen von Klassen und das Zuweisen von Abhängigkeiten ist Teil des Modellierungsprozesses, der unterhaltsamen Teile. Dies ist der Grund, warum die meisten Programmierer gerne programmieren.
Zu entscheiden, welche Implementierung verwendet wird und wie Klassen aufgebaut sind, muss auf irgendeine Weise implementiert werden und ist Teil der Anwendungskonfiguration.
Manuelles Schreiben von Fabriken ist eine mühsame Arbeit. DI-Frameworks vereinfachen dies, indem Abhängigkeiten, die möglicherweise aufgelöst werden, automatisch aufgelöst werden und für diejenigen, die dies nicht tun, eine Konfiguration erforderlich ist.
Mit modernen IDEs können Sie durch Methoden und deren Verwendung navigieren. Manuell geschriebene Fabriken sind sehr gut, da Sie einfach den Konstruktor einer Klasse markieren, seine Verwendung anzeigen und alle Stellen anzeigen können, an denen und wie die bestimmte Klasse erstellt wird.
Aber auch mit DI-Framework können Sie eine zentrale Stelle einnehmen, z. B. die Konfiguration der Objektdiagrammkonstruktion (ab sofort OGCC). Wenn eine Klasse von mehrdeutigen Abhängigkeiten abhängt, können Sie einfach in OGCC nachsehen, wie eine bestimmte Klasse konstruiert wird genau da.
Sie könnten einwenden, dass Sie persönlich der Meinung sind, dass es ein großes Plus ist, die Verwendung eines bestimmten Konstruktors zu sehen. Ich würde sagen, was für Sie besser herauskommt, ist nicht nur subjektiv, sondern kommt hauptsächlich aus persönlicher Erfahrung.
Wenn Sie immer an Projekten gearbeitet hätten, die sich auf DI-Frameworks stützten, würden Sie nur das wirklich wissen, und wenn Sie eine Konfiguration einer Klasse sehen wollten, würden Sie sich immer mit OGCC befassen und würden nicht einmal versuchen, zu sehen, wie die Konstruktoren verwendet werden , weil Sie wissen würden, dass die Abhängigkeiten sowieso alle automatisch verkabelt werden.
Natürlich können DI-Frameworks nicht für alles verwendet werden, und ab und zu müssen Sie eine oder zwei Factory-Methoden schreiben. Ein sehr häufiger Fall ist das Erstellen einer Abhängigkeit während der Iteration über eine Auflistung, wobei jede Iteration ein anderes Flag liefert, das eine andere Implementierung einer ansonsten gemeinsamen Schnittstelle erzeugen sollte.
Am Ende wird es höchstwahrscheinlich darauf ankommen, was Sie bevorzugen und was Sie opfern wollen (entweder wenn Sie Fabriken schreiben oder Transparenz).
Persönliche Erfahrung (Warnung: subjektiv)
Ich spreche als PHP-Entwickler, obwohl ich die Idee hinter DI-Frameworks mag, habe ich sie immer nur einmal verwendet. Damals sollte ein Team, mit dem ich zusammengearbeitet habe, eine Anwendung sehr schnell bereitstellen, und wir konnten keine Zeit verlieren, Fabriken zu schreiben. Also haben wir einfach ein DI-Framework ausgewählt, es konfiguriert und es hat irgendwie funktioniert .
Für die meisten Projekte lasse ich die Fabriken gerne manuell schreiben, weil mir die schwarze Magie in DI-Frameworks nicht gefällt. Ich war für die Modellierung einer Klasse und ihrer Abhängigkeiten verantwortlich. Ich war derjenige, der entschieden hat, warum das Design so aussieht, wie es aussieht. Also werde ich derjenige sein, der die Klasse richtig konstruiert (auch wenn die Klasse harte Abhängigkeiten hat, wie z. B. konkrete Klassen anstelle von Schnittstellen).
quelle
Persönlich benutze ich keine DI-Container und mag sie nicht. Ich habe noch ein paar Gründe dafür.
Normalerweise ist es eine statische Abhängigkeit. Es ist also nur ein Service-Finder mit all seinen Nachteilen. Aufgrund der Art der statischen Abhängigkeiten werden sie dann ausgeblendet. Sie müssen sich nicht mit ihnen auseinandersetzen, sie sind irgendwie schon da und es ist gefährlich, weil Sie aufhören, sie zu kontrollieren.
Es verstößt gegen OOP-Prinzipien wie Kohäsion und David Wests Lego-Brick-Objektmetapher.
Wenn Sie also das gesamte Objekt so nah wie möglich am Einstiegspunkt des Programms erstellen, ist dies der richtige Weg.
quelle
Wie Sie bereits bemerkt haben: Abhängig von der Sprache, die Sie verwenden, gibt es unterschiedliche Sichtweisen, und unterschiedliche Anforderungen an den Umgang mit ähnlichen Problemen.
In Bezug auf die Abhängigkeitsinjektion kommt es auf Folgendes an: Die Abhängigkeitsinjektion ist, wie der Begriff sagt, nichts anderes, als die Abhängigkeiten, die ein Objekt benötigt, in dieses Objekt einzufügen. Sie entkoppeln Objekterstellung und Objektverbrauch um mehr Flexibilität zu gewinnen.
Wenn Ihr Design gut strukturiert ist, gibt es in der Regel nur wenige Klassen mit vielen Abhängigkeiten. Daher sollte die Verkabelung von Abhängigkeiten - entweder von Hand oder durch ein Framework - relativ einfach sein und die Anzahl der zu schreibenden Testkessel sollte leicht zu handhaben sein.
Es ist jedoch kein DI-Framework erforderlich.
Aber warum wollen Sie trotzdem ein Framework verwenden?
I) Vermeiden Sie Boilerplate-Codes
Wenn Ihr Projekt eine Größe erreicht, bei der Sie immer wieder den Schmerz verspüren, Fabriken (und Instatiationen) zu schreiben, sollten Sie ein DI-Framework verwenden
II) Deklakrativer Programmieransatz
Als Java einmal mit XML programmierte , ist es jetzt: Mit Anmerkungen übergießen und die Magie geschieht .
Aber im Ernst: Ich sehe einen klaren Vorteil . Sie kommunizieren Ihre Absicht klar damit, zu sagen, was benötigt wird, anstatt wo es zu finden und wie es zu bauen ist.
tl; dr
DI-Frameworks verbessern Ihre Codebasis nicht auf magische Weise, aber sie sorgen dafür, dass gut aussehender Code wirklich klar aussieht . Verwenden Sie es, wenn Sie das Gefühl haben, es zu brauchen. Ab einem bestimmten Punkt in Ihrem Projekt sparen Sie Zeit und vermeiden Übelkeit.
quelle
Ja. Sie sollten wahrscheinlich Mark Seemanns Buch "Dependency Injection" lesen. Er skizziert einige gute Praktiken. Basierend auf einigen Ihrer Aussagen könnten Sie davon profitieren. Sie sollten eine Kompositionswurzel oder eine Kompositionswurzel pro Arbeitseinheit anstreben (z. B. Webanforderung). Ich mag seine Art zu denken, dass jedes Objekt, das Sie erstellen, als Abhängigkeit betrachtet wird (wie jeder Parameter einer Methode / eines Konstruktors), daher sollten Sie wirklich darauf achten, wo und wie Sie Objekte erstellen. Ein Framework hilft Ihnen dabei, diese Konzepte klar zu halten. Wenn Sie Marks Buch lesen, finden Sie mehr als nur die Antwort auf Ihre Frage.
quelle
Ja, wenn Sie in Java arbeiten, würde ich ein DI-Framework empfehlen. Dafür gibt es einige Gründe:
Java verfügt über mehrere äußerst gute DI-Pakete, die eine automatische Abhängigkeitsinjektion bieten, und in der Regel zusätzliche Funktionen enthalten, die schwerer manuell zu schreiben sind als einfache DI-Pakete (z. B. Abhängigkeiten mit Gültigkeitsbereichen, die eine automatische Verbindung zwischen Gültigkeitsbereichen ermöglichen, indem Code generiert wird, um nachzuschlagen, in welchem Gültigkeitsbereich sie sich befinden sollen) Verwenden Sie ein d, um die richtige Version der Abhängigkeit für diesen Bereich zu finden (z. B. können Objekte mit Anwendungsbereich auf Objekte mit Anforderungsbereich verweisen).
Java-Anmerkungen sind äußerst nützlich und bieten eine hervorragende Möglichkeit zum Konfigurieren von Abhängigkeiten
Die Java-Objektkonstruktion ist im Vergleich zu dem, was Sie in Python gewohnt sind, überaus ausführlich. Die Verwendung eines Frameworks spart hier mehr Arbeit als in einer dynamischen Sprache.
Java DI-Frameworks können nützliche Dinge tun, z. B. das automatische Generieren von Proxys und Dekoratoren, die in Java mehr funktionieren als eine dynamische Sprache.
quelle
Wenn Ihr Projekt klein genug ist und Sie nicht viel Erfahrung mit dem DI-Framework haben, würde ich empfehlen, das Framework nicht zu verwenden und dies manuell zu tun.
Grund: Ich habe einmal in einem Projekt gearbeitet, in dem zwei Bibliotheken verwendet wurden, die direkte Abhängigkeiten zu verschiedenen Di-Frameworks hatten. Beide hatten einen Framework-spezifischen Code wie di-give-me-an-instance-of-XYZ. Soweit ich weiß, können Sie immer nur eine aktive Framework-Implementierung gleichzeitig haben.
Mit Code wie diesem können Sie mehr Schaden anrichten als profitieren.
Wenn Sie mehr Erfahrung haben, würden Sie wahrscheinlich die Verzerrung durch eine Schnittstelle (Fabrik oder Servicelocator) entfernen, so dass dieses Problem nicht mehr besteht und das di-Framework diesem Schaden mehr Nutzen bringt.
quelle