Was sind die Nachteile der Verwendung von Dependency Injection? [geschlossen]

336

Ich versuche, DI hier bei der Arbeit als Muster einzuführen, und einer unserer Hauptentwickler möchte wissen: Was sind - wenn überhaupt - die Nachteile der Verwendung des Abhängigkeitsinjektionsmusters?

Hinweis Ich suche hier nach einer - wenn möglich - vollständigen Liste, nicht nach einer subjektiven Diskussion zu diesem Thema.


Klarstellung : Ich spreche über die Dependency Injection - Muster (siehe diesen Artikel von Martin Fowler), nicht ein spezifischen Rahmen, ob XML-basierte (wie Spring) oder Code-Basis (wie Guice) oder „selbst gerollt“ .


Bearbeiten : Einige große weitere Diskussion / ranting / Debatte los / r / Programmierung hier.

Epaga
quelle
Es kann eine gute Idee sein, anzugeben, ob wir DI selbst oder eine bestimmte Art von Tool diskutieren sollen, das dies unterstützt (XML-basiert oder nicht).
Jesse Millikan
5
Der Unterschied besteht darin, dass ich NICHT nach Antworten suche, die nur für bestimmte Frameworks gelten, wie z. B. "XML sucks". :) Ich suche nach Antworten, die für das von Fowler skizzierte DI-Konzept gelten: martinfowler.com/articles/injection.html
Epaga
3
Ich sehe überhaupt keine Nachteile von DI im Allgemeinen (Konstruktor, Setter oder Methode). Es entkoppelt und vereinfacht ohne Overhead.
Kissaki
2
@ Kissaki gut abgesehen von dem Setter injizieren Zeug. Am besten Objekte herstellen, die beim Bau verwendbar sind. Wenn Sie mir nicht glauben, versuchen Sie einfach, einem Objekt mit 'Setter'-Injektion einen weiteren Mitarbeiter hinzuzufügen. Sie erhalten mit Sicherheit eine NPE ...
time4tea

Antworten:

209

Ein paar Punkte:

  • DI erhöht die Komplexität, normalerweise durch Erhöhen der Anzahl der Klassen, da die Verantwortlichkeiten stärker voneinander getrennt sind, was nicht immer von Vorteil ist
  • Ihr Code wird (etwas) an das von Ihnen verwendete Abhängigkeitsinjektionsframework gekoppelt (oder allgemeiner an die Art und Weise, wie Sie das DI-Muster implementieren).
  • DI-Container oder Ansätze, die eine Typauflösung durchführen, verursachen im Allgemeinen eine geringfügige Laufzeitstrafe (sehr vernachlässigbar, aber vorhanden).

Im Allgemeinen erleichtert der Vorteil der Entkopplung das Lesen und Verstehen jeder Aufgabe, erhöht jedoch die Komplexität der Orchestrierung der komplexeren Aufgaben.

Håvard S.
quelle
72
- Die Trennung von Klassen reduziert die Komplexität. Viele Klassen machen eine Anwendung nicht komplex. - Sie sollten nur eine Abhängigkeit von Ihrem DI-Framework im Anwendungsstamm haben.
Robert
91
Wir sind Menschen; Unser Kurzzeitgedächtnis ist begrenzt und kann nicht viele <xxx> gleichzeitig verarbeiten. Dies gilt für Klassen genauso wie für Methoden, Funktionen, Dateien oder andere Konstrukte, mit denen Sie Ihr Programm entwickeln.
Håvard S
45
@ Havard S: Ja, aber 5 wirklich komplexe Klassen sind nicht einfacher als 15 einfache. Die Möglichkeit, die Komplexität zu verringern, besteht darin, den Arbeitssatz zu reduzieren, der vom Programmierer benötigt wird, der an dem Code arbeitet - komplexe, stark voneinander abhängige Klassen erreichen dies nicht.
Kyoryu
35
@kyoryu Ich denke, wir sind uns alle einig. Beachten Sie, dass Komplexität nicht mit Kopplung identisch ist . Eine abnehmende Kopplung kann die Komplexität erhöhen. Ich sage wirklich nicht, dass DI eine schlechte Sache ist, weil es die Anzahl der Klassen erhöht. Ich hebe die möglichen Nachteile hervor, die damit verbunden sind. :)
Håvard S
96
+1 für "DI erhöht die Komplexität" Das gilt für DI und darüber hinaus. (Fast) jedes Mal, wenn wir die Flexibilität erhöhen, werden wir die Komplexität erhöhen. Es geht um Balance, die damit beginnt, die Vor- und Nachteile zu kennen. Wenn Leute sagen: "Es gibt keine Nachteile", ist dies ein sicherer Indikator dafür, dass sie die Sache noch nicht vollständig verstanden haben.
Don Branson
183

Das gleiche Grundproblem tritt häufig bei objektorientierter Programmierung, Stilregeln und fast allem anderen auf. Es ist möglich - in der Tat sehr häufig -, zu viel Abstraktion zu betreiben, zu viel Indirektion hinzuzufügen und im Allgemeinen gute Techniken übermäßig und an den falschen Stellen anzuwenden.

Jedes Muster oder andere Konstrukt, das Sie anwenden, bringt Komplexität mit sich. Abstraktion und Indirektion streuen Informationen herum, wobei manchmal irrelevante Details aus dem Weg geräumt werden, aber manchmal wird es auch schwieriger, genau zu verstehen, was passiert. Jede Regel, die Sie anwenden, bringt Unflexibilität mit sich und schließt Optionen aus, die möglicherweise der beste Ansatz sind.

Der Punkt ist, Code zu schreiben, der die Arbeit erledigt und robust, lesbar und wartbar ist. Sie sind ein Softwareentwickler - kein Elfenbeinturmbauer.

Relevante Links

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


Die wahrscheinlich einfachste Form der Abhängigkeitsinjektion (nicht lachen) ist ein Parameter. Der abhängige Code ist von Daten abhängig, und diese Daten werden durch Übergabe des Parameters eingefügt.

Ja, es ist albern und spricht nicht den objektorientierten Punkt der Abhängigkeitsinjektion an, aber ein funktionaler Programmierer wird Ihnen sagen, dass dies (wenn Sie erstklassige Funktionen haben) die einzige Art der Abhängigkeitsinjektion ist, die Sie benötigen. Hier geht es darum, ein triviales Beispiel zu nehmen und die möglichen Probleme aufzuzeigen.

Nehmen wir diese einfache traditionelle Funktion - die C ++ - Syntax ist hier nicht von Bedeutung, aber ich muss sie irgendwie buchstabieren ...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

Ich habe eine Abhängigkeit, die ich extrahieren und injizieren möchte - den Text "Hello World". Leicht genug...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

Wie ist das unflexibler als das Original? Was ist, wenn ich beschließe, dass die Ausgabe Unicode sein soll? Ich möchte wahrscheinlich von std :: cout zu std :: wcout wechseln. Aber das bedeutet, dass meine Saiten dann von wchar_t sein müssen, nicht von char. Entweder muss jeder Aufrufer geändert werden, oder (vernünftigerweise) die alte Implementierung wird durch einen Adapter ersetzt, der die Zeichenfolge übersetzt und die neue Implementierung aufruft.

Das sind genau dort Wartungsarbeiten, die nicht nötig wären, wenn wir das Original behalten hätten.

Und wenn es trivial erscheint, werfen Sie einen Blick auf diese reale Funktion aus der Win32-API ...

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

Das sind 12 "Abhängigkeiten", mit denen man sich befassen muss. Wenn beispielsweise die Bildschirmauflösungen sehr groß werden, benötigen wir möglicherweise 64-Bit-Koordinatenwerte - und eine andere Version von CreateWindowEx. Und ja, es hängt bereits eine ältere Version herum, die vermutlich hinter den Kulissen der neueren Version zugeordnet wird ...

http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

Diese "Abhängigkeiten" sind nicht nur für den ursprünglichen Entwickler ein Problem. Jeder, der diese Schnittstelle verwendet, muss nachsehen, welche Abhängigkeiten bestehen, wie sie angegeben sind und was sie bedeuten, und herausfinden, was für seine Anwendung zu tun ist. Hier können die Worte "sinnvolle Vorgaben" das Leben viel einfacher machen.

Die objektorientierte Abhängigkeitsinjektion unterscheidet sich im Prinzip nicht. Das Schreiben einer Klasse ist sowohl im Quellcodetext als auch in der Entwicklerzeit ein Overhead. Wenn diese Klasse so geschrieben wird, dass Abhängigkeiten gemäß einigen Spezifikationen für abhängige Objekte bereitgestellt werden, kann das abhängige Objekt diese Schnittstelle nicht unterstützen, selbst wenn dies erforderlich ist um die Implementierung dieses Objekts zu ersetzen.

Nichts davon sollte als Behauptung verstanden werden, dass die Abhängigkeitsinjektion schlecht ist - weit davon entfernt. Aber jede gute Technik kann übermäßig und am falschen Ort angewendet werden. So wie nicht jeder String extrahiert und in einen Parameter umgewandelt werden muss, muss nicht jedes Verhalten auf niedriger Ebene aus Objekten auf hoher Ebene extrahiert und in eine injizierbare Abhängigkeit umgewandelt werden.

Steve314
quelle
3
Die Abhängigkeitsinjektion weist keinen dieser Nachteile auf. Es ändert nur die Art und Weise, wie Sie Abhängigkeiten an Objekte übergeben, was weder Komplexität noch Inflexibilität erhöht. Ganz im Gegenteil.
Kissaki
24
@Kissaki - ja, es ändert die Art und Weise, wie Sie Ihre Abhängigkeiten übergeben. Und Sie müssen Code schreiben, um diese Übergabe von Abhängigkeiten zu handhaben. Und bevor Sie dies tun, müssen Sie herausfinden, welche Abhängigkeiten übergeben werden sollen, sie so definieren, dass sie für jemanden sinnvoll sind, der den abhängigen Code nicht codiert hat usw. Sie können Auswirkungen auf die Laufzeit vermeiden (z. B. mithilfe von Richtlinienparametern für Vorlagen in C ++), aber das ist immer noch Code, den Sie schreiben und pflegen müssen. Wenn es gerechtfertigt ist, ist es ein sehr kleiner Preis für eine große Belohnung, aber wenn Sie so tun, als gäbe es keinen Preis zu zahlen, bedeutet dies nur, dass Sie diesen Preis zahlen, wenn es keinen Gewinn gibt.
Steve314
6
@Kissaki - Was die Inflexibilität betrifft, sind Sie, sobald Sie Ihre Abhängigkeiten angegeben haben, daran gebunden. Andere Personen haben Abhängigkeiten geschrieben, die gemäß dieser Spezifikation injiziert werden sollen. Wenn Sie eine neue Implementierung für den abhängigen Code benötigen, für die geringfügig andere Abhängigkeiten erforderlich sind - schwierig, sind Sie festgefahren. Es ist an der Zeit, stattdessen einige Adapter für diese Abhängigkeiten zu schreiben (und etwas mehr Overhead und eine weitere Abstraktionsebene).
Steve314
Ich bin mir nicht sicher, ob ich dieses Beispiel verstehe, wenn Sie eine Überladung in Betracht ziehen. Um das Literal "Hello World" auszugeben, verwenden wir die Signatur (). Verwenden Sie zum Ausgeben einer 8-Bit-Zeichenfolge die Signatur (char). Zur Ausgabe von Unicode die Überladung (wchar_t). In diesem Fall ist (wchar_t) offensichtlich derjenige, den die anderen hinter den Kulissen aufrufen. Dort ist nicht viel Code-Umschreiben erforderlich. Vermisse ich etwas
ingredient_15939
2
@ Zutat_15939 - Ja, dies ist ein vereinfachtes Spielzeugbeispiel. Wenn Sie dies wirklich tun würden, würden Sie nur eine Überladung anstelle eines Adapters verwenden, da die Kosten für das Duplizieren des Codes geringer sind als die Kosten des Adapters. Beachten Sie jedoch, dass das Duplizieren von Code im Allgemeinen eine schlechte Sache ist und die Verwendung von Adaptern zur Vermeidung des Duplizierens von Code eine weitere gute Technik ist (die manchmal zu häufig und an den falschen Stellen verwendet werden kann).
Steve314
77

Hier ist meine erste Reaktion: Grundsätzlich die gleichen Nachteile eines Musters.

  • Es braucht Zeit, um zu lernen
  • Wenn es missverstanden wird, kann es mehr schaden als nützen
  • Im Extremfall kann es mehr Arbeit sein, als den Nutzen rechtfertigen würde
Epaga
quelle
2
Warum braucht es Zeit zum Lernen? Ich dachte, es macht die Dinge im Vergleich zu ejb einfach.
Fastcodejava
8
Dies scheint von Hand gewellt zu sein, wenn man bedenkt, dass man keine subjektive Diskussion möchte. Ich schlage vor, (gegebenenfalls gemeinsam) ein realistisches Beispiel für die Prüfung zu erstellen. Das Erstellen einer Stichprobe ohne DI wäre ein guter Ausgangspunkt. Dann könnten wir es herausfordern, Änderungen an den Anforderungen vorzunehmen und die Auswirkungen zu untersuchen. (Dies ist äußerst praktisch für mich, um übrigens vorzuschlagen, wie ich ins Bett gehen werde.)
Jesse Millikan
4
Dies braucht wirklich einige Beispiele, um dies zu belegen, und nachdem ich Dutzenden von Menschen DI beigebracht habe, kann ich Ihnen sagen, dass es wirklich ein sehr einfaches Konzept ist, es zu lernen und in die Praxis umzusetzen.
Chillitom
4
Ich halte DI nicht wirklich für ein Muster. Es (in Verbindung mit IoC) ist eigentlich eher ein Programmiermodell - wenn Sie es vollständig befolgen, sieht Ihr Code viel actor-ähnlicher aus als "typisches" pseudo-prozedurales OO.
Kyoryu
1
+1 Ich verwende Symfony 2, der DI ist über den gesamten Benutzercode verteilt und verwendet nur ihn.
MGP
45

Der größte "Nachteil" von Inversion of Control (nicht ganz DI, aber nah genug) ist, dass es dazu neigt, einen einzelnen Punkt zu entfernen, um einen Überblick über einen Algorithmus zu erhalten. Das passiert im Grunde genommen, wenn Sie Code entkoppelt haben - die Fähigkeit, an einer Stelle zu suchen, ist ein Artefakt der engen Kopplung.

Kyoryu
quelle
2
Aber dieser "Nachteil" liegt sicherlich in der Natur des Problems, das wir lösen. Wenn wir es entkoppeln, damit wir die Implementierung leicht ändern können, gibt es keinen Ort, an dem wir uns umsehen können, und welche Relevanz hat es, es betrachten zu können? Der einzige Fall, den ich denken kann, ist das Debuggen, und die Debugging-Umgebung sollte in der Lage sein, in die Implementierung einzusteigen.
Vickirk
3
Aus diesem Grund wurde das Wort "Nachteil" in Anführungszeichen gesetzt :) Lose Kopplung und starke Einkapselung schließen per Definition "einen Ort, an dem man alles sehen kann" aus. Sehen Sie meine Kommentare zur Antwort von Havard S, wenn Sie das Gefühl haben, dass ich gegen DI / IoC bin.
Kyoryu
1
Ich stimme zu (ich habe dich sogar gewählt). Ich habe nur darauf hingewiesen.
Vickirk
1
@ Vickirk: Der Nachteil ist, dass es für einen Menschen schwer zu verstehen ist. Das Entkoppeln von Dingen erhöht die Komplexität in dem Sinne, dass es für Menschen schwieriger ist, sie zu verstehen, und dass es länger dauert, sie vollständig zu verstehen.
Bjarke Freund-Hansen
2
Im Gegenteil, ein Vorteil von DI besteht darin, dass Sie sich den Konstruktor einer einzelnen Klasse ansehen und sofort herausfinden können, von welchen Klassen sie abhängt (dh welche Klassen ihre Arbeit erledigen müssen). Dies ist für anderen Code viel schwieriger, da der Code wohl oder übel Klassen erstellen kann.
Contango
42

Ich glaube nicht, dass eine solche Liste existiert, aber versuchen Sie, diese Artikel zu lesen:

Gabriel Ščerbák
quelle
7
Ich bin neugierig, wenn jemand nach Wonsides fragt, warum Antworten im Geiste "es gibt keine" positiv bewertet werden und solche, die einige Informationen zu der Frage enthalten, nicht?
Gabriel Ščerbák
12
-1 Da ich nicht nach einer Linksammlung suche, suche ich nach tatsächlichen Antworten: Wie wäre es, wenn Sie die Punkte der einzelnen Artikel zusammenfassen? Dann würde ich stattdessen upvoten. Beachten Sie, dass die Artikel selbst ziemlich interessant sind, obwohl es scheint, dass die ersten beiden tatsächlich Negative von DI entlarven?
Epaga
4
Ich frage mich, ob die am besten bewertete Antwort auch abgelehnt wurde, weil sie die Frage imho wirklich überhaupt nicht beantwortet :) Klar, ich weiß, was Sie gefragt haben und was ich beantwortet habe, ich bitte nicht um positive Stimmen, ich bin nur verwirrt, warum meine Die Antwort hilft nicht, während DI anscheinend sagt, dass der Nachteil, dass es zu cool ist, viel hilft.
Gabriel Ščerbák
41

Ich habe Guice (Java DI Framework) in den letzten 6 Monaten ausgiebig verwendet. Insgesamt finde ich es großartig (insbesondere aus Testsicht), aber es gibt gewisse Nachteile. Vor allem:

  • Code kann schwieriger zu verstehen sein. Die Abhängigkeitsinjektion kann auf sehr ... kreative ... Weise verwendet werden. Zum Beispiel bin ich gerade auf Code gestoßen, der eine benutzerdefinierte Anmerkung zum Einfügen bestimmter IOStreams verwendet hat (z. B. @ Server1Stream, @ Server2Stream). Während dies funktioniert und ich zugeben muss, dass es eine gewisse Eleganz hat, macht es das Verständnis der Guice-Injektionen zu einer Voraussetzung für das Verständnis des Codes.
  • Höhere Lernkurve beim Lernprojekt. Dies bezieht sich auf Punkt 1. Um zu verstehen, wie ein Projekt mit Abhängigkeitsinjektion funktioniert, müssen Sie sowohl das Abhängigkeitsinjektionsmuster als auch das spezifische Framework verstehen. Als ich bei meinem jetzigen Job anfing, verbrachte ich einige verwirrte Stunden damit, herauszufinden, was Guice hinter den Kulissen tat.
  • Konstruktoren werden groß. Dies kann zwar weitgehend mit einem Standardkonstruktor oder einer Factory behoben werden.
  • Fehler können verschleiert werden. Mein jüngstes Beispiel dafür war, dass ich eine Kollision mit zwei Flaggennamen hatte. Guice schluckte den Fehler lautlos und eine meiner Flaggen wurde nicht initialisiert.
  • Fehler werden zur Laufzeit verschoben. Wenn Sie Ihr Guice-Modul falsch konfigurieren (Zirkelverweis, fehlerhafte Bindung, ...), werden die meisten Fehler während der Kompilierungszeit nicht aufgedeckt. Stattdessen werden die Fehler angezeigt, wenn das Programm tatsächlich ausgeführt wird.

Jetzt wo ich mich beschwert habe. Lassen Sie mich sagen, dass ich Guice weiterhin (bereitwillig) in meinem aktuellen Projekt und höchstwahrscheinlich in meinem nächsten verwenden werde. Die Abhängigkeitsinjektion ist ein großartiges und unglaublich leistungsfähiges Muster. Aber es kann definitiv verwirrend sein und Sie werden mit ziemlicher Sicherheit einige Zeit damit verbringen, über das von Ihnen gewählte Abhängigkeitsinjektions-Framework zu fluchen.

Ich stimme auch anderen Postern zu, dass die Abhängigkeitsinjektion überbeansprucht werden kann.

Andy Peck
quelle
14
Ich verstehe nicht, warum die Leute für mich nicht mehr "Fehler werden zur Laufzeit verschoben" machen, das ist ein Deal Breaker. Statische Tipp- und Kompilierungsfehler sind das größte Geschenk, das Entwicklern gegeben wird, das ich nicht werfen würde sie für alles weg
Richard Tingle
2
@RichardTingle, IMO während des Starts der App DI-Module werden zuerst initialisiert, sodass jede Fehlkonfiguration im Modul sichtbar wird, sobald die App startet, und nicht nach einigen Tagen oder Zeiten. Es ist auch möglich, Module inkrementell zu laden. Wenn wir uns jedoch an den Geist von DI halten, indem wir das Laden des Moduls vor dem Initialisieren der Anwendungslogik begrenzen, können wir die fehlerhaften Bindungen erfolgreich auf den Start der App isolieren. Aber wenn wir es als Service Locator Anti-Pattern konfigurieren, werden diese schlechten Bindungen sicherlich eine Überraschung sein.
K4vin
@RichardTingle Ich verstehe, dass es niemals dem vom Compiler bereitgestellten Sicherheitsnetz ähnlich sein wird, aber DI als korrekt verwendetes Tool, wie in der Box angegeben, dann sind diese Laufzeitfehler auf die App-Initialisierung beschränkt. Dann können wir die App-Initialisierung als eine Art Kompilierungsphase für DI-Module betrachten. Nach meiner Erfahrung gibt es die meiste Zeit, wenn die App startet, keine schlechten Bindungen oder falschen Referenzen. PS - Ich habe NInject mit C #
k4vin
@RichardTingle - Ich stimme Ihnen zu, aber es ist ein Kompromiss, um lose gekoppelten, also testbaren Code zu erhalten. Und wie k4vin sagte, werden bei der Initialisierung fehlende Abhängigkeiten gefunden, und die Verwendung von Schnittstellen hilft weiterhin bei Fehlern bei der Kompilierung.
Andrei Epure
1
Ich würde "Schwer, den Code sauber zu halten" in Ihre Liste aufnehmen. Sie wissen nie, ob Sie eine Registrierung löschen können, bevor Sie den Code ohne ihn ausführlich testen.
Fernacolo
24

Code ohne DI birgt das bekannte Risiko, sich in Spaghetti-Code zu verwickeln Einige Symptome sind, dass die Klassen und Methoden zu groß sind, zu viel bewirken und nicht einfach geändert, aufgeschlüsselt, überarbeitet oder getestet werden können.

Code, bei dem DI häufig verwendet wird, kann Ravioli-Code sein, bei dem jede kleine Klasse wie ein einzelnes Ravioli-Nugget ist - er macht eine kleine Sache und das Prinzip der Einzelverantwortung wird eingehalten, was gut ist. Wenn man sich die Klassen alleine ansieht, ist es schwer zu erkennen, was das System als Ganzes tut, da dies davon abhängt, wie all diese vielen kleinen Teile zusammenpassen, was schwer zu erkennen ist. Es sieht aus wie ein großer Haufen kleiner Dinge.

Indem Sie die Spaghetti-Komplexität großer Teile gekoppelten Codes innerhalb einer großen Klasse vermeiden, laufen Sie Gefahr, eine andere Art von Komplexität zu entwickeln, bei der es viele einfache kleine Klassen gibt und die Interaktionen zwischen ihnen komplex sind.

Ich denke nicht, dass dies ein fataler Nachteil ist - DI ist immer noch sehr lohnenswert. Ein gewisses Maß an Ravioli-Stil mit kleinen Klassen, die nur eines tun, ist wahrscheinlich gut. Selbst im Übermaß denke ich nicht, dass es als Spaghetti-Code schlecht ist. Das Bewusstsein, dass es zu weit gehen kann, ist der erste Schritt, um dies zu vermeiden. Folgen Sie den Links, um zu besprechen, wie Sie dies vermeiden können.

Anthony
quelle
1
Ja, ich mag diesen Begriff "Ravioli-Code"; Ich drücke es langatmiger aus, wenn ich über die Nachteile von DI spreche. Trotzdem kann ich mir nicht vorstellen, ein echtes Framework oder eine echte Anwendung in Java ohne DI zu entwickeln.
Howard M. Lewis Schiff
13

Wenn Sie eine selbst entwickelte Lösung haben, sind die Abhängigkeiten im Konstruktor direkt in Ihrem Gesicht. Oder vielleicht als Methodenparameter, die wiederum nicht allzu schwer zu erkennen sind. Obwohl Framework-verwaltete Abhängigkeiten, wenn sie auf die Spitze getrieben werden, wie Magie erscheinen können.

Zu viele Abhängigkeiten in zu vielen Klassen sind jedoch ein klares Zeichen dafür, dass Ihre Klassenstruktur durcheinander ist. In gewisser Weise kann die Abhängigkeitsinjektion (selbst entwickelt oder vom Framework verwaltet) dazu beitragen, eklatante Designprobleme zu lösen, die ansonsten im Dunkeln lauern könnten.


Um den zweiten Punkt besser zu veranschaulichen, hier ein Auszug aus diesem Artikel ( Originalquelle ), von dem ich von ganzem Herzen glaube, dass er das grundlegende Problem beim Aufbau eines Systems ist, nicht nur von Computersystemen.

Angenommen, Sie möchten einen College-Campus entwerfen. Sie müssen einen Teil des Entwurfs an die Studenten und Professoren delegieren, da sonst das Physikgebäude für die Physiker nicht gut funktioniert. Kein Architekt weiß genug darüber, was die Physiker brauchen, um alles selbst zu machen. Sie können jedoch nicht das Design jedes Raums an seine Bewohner delegieren, da Sie dann einen riesigen Trümmerhaufen erhalten.

Wie können Sie die Verantwortung für das Design auf alle Ebenen einer großen Hierarchie verteilen und gleichzeitig die Konsistenz und Harmonie des gesamten Designs gewährleisten? Dies ist das architektonische Entwurfsproblem, das Alexander zu lösen versucht, aber es ist auch ein grundlegendes Problem der Entwicklung von Computersystemen.

Löst DI dieses Problem? Nein . Aber es hilft Ihnen, klar zu erkennen, ob Sie versuchen, die Verantwortung für die Gestaltung jedes Raums an seine Bewohner zu delegieren.

Anurag
quelle
13

Eine Sache, die mich ein wenig mit DI winden lässt, ist die Annahme, dass alle injizierten Objekte billig zu instanziieren sind und keine Nebenwirkungen hervorrufen ODER - die Abhängigkeit wird so häufig verwendet, dass sie die damit verbundenen Instanziierungskosten überwiegt.

Dies kann von Bedeutung sein, wenn eine Abhängigkeit innerhalb einer konsumierenden Klasse nicht häufig verwendet wird. wie so etwas wie ein IExceptionLogHandlerService. Offensichtlich wird ein solcher Dienst (hoffentlich :) selten innerhalb der Klasse aufgerufen - vermutlich nur bei Ausnahmen, die protokolliert werden müssen. doch das kanonische Konstruktor-Injektionsmuster ...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

... erfordert, dass eine "Live" -Instanz dieses Dienstes bereitgestellt wird, verdammt die Kosten / Nebenwirkungen, die erforderlich sind, um ihn dorthin zu bringen. Nicht, dass dies wahrscheinlich wäre, aber was wäre, wenn das Erstellen dieser Abhängigkeitsinstanz einen Dienst- / Datenbanktreffer oder das Nachschlagen von Konfigurationsdateien oder das Sperren einer Ressource bis zur Entsorgung beinhalten würde? Wenn dieser Service stattdessen nach Bedarf, Service-Standort oder werksseitig erstellt wurde (alle haben ihre eigenen Probleme), würden Sie die Baukosten nur bei Bedarf übernehmen.

Nun ist es ein allgemein anerkannte Software - Design - Prinzip , dass ein Objekt konstruieren ist billig und nicht Nebenwirkungen zu erzeugen. Und obwohl das eine nette Vorstellung ist, ist es nicht immer der Fall. Die Verwendung einer typischen Konstruktorinjektion erfordert jedoch grundsätzlich, dass dies der Fall ist. Das heißt, wenn Sie eine Implementierung einer Abhängigkeit erstellen, müssen Sie diese unter Berücksichtigung von DI entwerfen. Vielleicht hätten Sie die Objektkonstruktion teurer gemacht, um anderswo Vorteile zu erzielen, aber wenn diese Implementierung injiziert wird, werden Sie wahrscheinlich gezwungen sein, dieses Design zu überdenken.

Übrigens können bestimmte Techniken genau dieses Problem mindern, indem sie das verzögerte Laden injizierter Abhängigkeiten ermöglichen, z. B. das Bereitstellen einer Lazy<IService>Instanz einer Klasse als Abhängigkeit. Dies würde die Konstruktoren Ihrer abhängigen Objekte ändern und dann die Implementierungsdetails wie die Kosten für die Objektkonstruktion noch besser kennen, was wahrscheinlich auch nicht wünschenswert ist.

ckittel
quelle
1
Ich verstehe, was Sie sagen, aber ich denke nicht, dass es fair ist zu sagen, "wenn Sie eine Implementierung einer Abhängigkeit erstellen, müssen Sie sie unter Berücksichtigung von DI entwerfen" - ich denke, eine genauere Aussage wäre "DI funktioniert am besten bei Verwendung mit Klassen, die mit Best Practices hinsichtlich der Instanziierungskosten und des Fehlens von Nebenwirkungen oder Fehlermodi während der Erstellung implementiert werden ". Im schlimmsten Fall können Sie immer eine verzögerte Proxy-Implementierung einfügen, die die Zuordnung des realen Objekts bis zur ersten Verwendung verzögert.
Jolly Roger
1
In jedem modernen IoC-Container können Sie die Lebensdauer eines Objekts für einen bestimmten abstrakten Typ / eine bestimmte abstrakte Schnittstelle angeben (immer eindeutig, Singleton, http-Bereich usw.). Sie können auch eine Factory-Methode / einen Factory-Delegaten bereitstellen, um sie mithilfe von Func <T> oder Lazy <T> träge zu instanziieren.
Dmitry S.
Genau richtig. Das Instanziieren von Abhängigkeiten kostet unnötig Speicher. Normalerweise verwende ich "Lazy-Loading" mit Gettern, die eine Klasse nur instanziieren, wenn sie aufgerufen werden. Sie können standardmäßig einen Konstruktor ohne Parameter sowie nachfolgende Konstruktoren bereitstellen, die instanziierte Abhängigkeiten akzeptieren. Im ersten Fall wird eine Klasse, die beispielsweise IErrorLogger implementiert, nur instanziiert, this.errorLogger.WriteError(ex)wenn in einer try / catch-Anweisung ein Fehler auftritt.
John Bonfardeci
12

Die Illusion, dass Sie Ihren Code entkoppelt haben, indem Sie die Abhängigkeitsinjektion implementiert haben, ohne ihn WIRKLICH zu entkoppeln. Ich denke, das ist das Gefährlichste an DI.

Joey Guerra
quelle
11

Dies ist eher ein Trottel. Einer der Nachteile der Abhängigkeitsinjektion besteht jedoch darin, dass es für Entwicklungstools etwas schwieriger ist, über Code nachzudenken und darin zu navigieren.

Wenn Sie bei gedrückter Ctrl-Taste / Befehlstaste auf einen Methodenaufruf im Code klicken, gelangen Sie anstelle der konkreten Implementierung zur Methodendeklaration auf einer Schnittstelle.

Dies ist eher ein Nachteil von lose gekoppeltem Code (Code, der von der Schnittstelle entworfen wurde) und gilt auch dann, wenn Sie keine Abhängigkeitsinjektion verwenden (dh selbst wenn Sie einfach Fabriken verwenden). Aber das Aufkommen der Abhängigkeitsinjektion hat wirklich dazu beigetragen, lose Code an die Massen zu koppeln, also dachte ich, ich würde es erwähnen.

Auch die Vorteile von lose gekoppeltem Code überwiegen bei weitem, daher nenne ich es einen Nitpick. Obwohl ich lange genug gearbeitet habe, um zu wissen, dass dies die Art von Push-Back ist, die Sie erhalten können, wenn Sie versuchen, eine Abhängigkeitsinjektion einzuführen.

In der Tat würde ich vermuten, dass Sie für jeden "Nachteil", den Sie für die Abhängigkeitsinjektion finden können, viele Vorteile finden, die ihn bei weitem überwiegen.

Jack Leow
quelle
5
Strg-Umschalt-B mit Resharper bringt Sie zur Implementierung
Adrianm
1
Aber andererseits hätten wir (in einer idealen Welt) nicht mindestens 2 Implementierungen von allem, dh mindestens eine echte Implementierung und eine
Scheinimplementierung
1
Wenn Sie in Eclipse die Strg-Taste gedrückt halten und den Mauszeiger über den Methodenaufrufcode halten, wird ein Menü angezeigt, in dem Sie entweder die Deklaration oder die Implementierung öffnen können.
Sam Hasler
Wenn Sie sich an der Schnittstellendefinition befinden, markieren Sie den Methodennamen und drücken Sie Strg + T, um die Typhierarchie für diese Methode aufzurufen. Jeder Implementierer dieser Methode wird in einem Typhierarchiebaum angezeigt.
Qualidafial
10

Konstruktorbasierte Abhängigkeitsinjektion (ohne die Hilfe magischer "Frameworks") ist eine saubere und vorteilhafte Methode zur Strukturierung von OO-Code. In den besten Codebasen, die ich gesehen habe, bemerkte ich über Jahre hinweg, die ich mit anderen Ex-Kollegen von Martin Fowler verbracht hatte, dass die meisten guten Klassen, die auf diese Weise geschrieben wurden, eine einzige doSomethingMethode haben.

Der größte Nachteil ist also, dass Ihre Motivation, OO-Code zu schreiben, schnell verfliegen kann, wenn Sie erst einmal feststellen, dass es sich nur um eine klobige, langhändige OO-Methode handelt, Abschlüsse als Klassen zu schreiben, um die Vorteile der funktionalen Programmierung zu nutzen.

sanityinc
quelle
1
Problem mit Konstruktor-basierten ist, dass Sie immer mehr Konstruktor-Argumente hinzufügen müssen ...
Miguel Ping
1
Haha. Wenn Sie das zu viel tun, werden Sie bald feststellen, dass Ihr Code beschissen ist, und Sie werden ihn umgestalten. Außerdem ist Strg-F6 nicht so schwer.
time4tea
Und natürlich ist auch das Gegenteil der Fall: Es ist alles nur eine klobige, langhändige und funktionale Art, Klassen als Abschlüsse zu schreiben, um die Vorteile der OO-Programmierung zu nutzen
CurtainDog
Vor vielen Jahren habe ich ein Objektsystem mit Verschlüssen in Perl erstellt, also verstehe ich, was Sie sagen, aber das Einkapseln von Daten ist kein einzigartiger Vorteil der OO-Programmierung. Daher ist nicht klar, welche vorteilhaften Funktionen von OO vergleichbar sind klobig und langhändig in einer funktionalen Sprache zu erhalten.
Sanityinc
9

Ich finde, dass die Konstruktorinjektion zu großen hässlichen Konstruktoren führen kann (und ich verwende sie in meiner gesamten Codebasis - vielleicht sind meine Objekte zu detailliert?). Außerdem habe ich manchmal bei der Konstruktorinjektion schreckliche zirkuläre Abhängigkeiten (obwohl dies sehr selten ist), sodass Sie möglicherweise einen Bereitschaftslebenszyklus mit mehreren Runden der Abhängigkeitsinjektion in einem komplexeren System haben müssen.

Ich bevorzuge jedoch die Construtor-Injektion gegenüber der Setter-Injektion, da ich nach dem Aufbau meines Objekts ohne Zweifel weiß, in welchem ​​Zustand es sich befindet, ob es sich in einer Unit-Test-Umgebung befindet oder in einem IOC-Container geladen ist. Was auf Umwegen sagt, was ich für den Hauptnachteil der Setter-Injektion halte.

(Als Nebenbemerkung finde ich das ganze Thema ziemlich "religiös", aber Ihre Laufleistung hängt vom technischen Eifer Ihres Entwicklerteams ab!)

James B.
quelle
8
Wenn Sie große hässliche Konstruktoren haben, sind Ihre Klassen möglicherweise zu groß und Sie haben nur zu viele Abhängigkeiten?
Robert
4
Das ist sicherlich eine Möglichkeit, die ich unterhalten möchte! ... Leider habe ich kein Team, mit dem ich Peer Reviews durchführen kann. Geigen spielen leise im Hintergrund, und dann wird der Bildschirm schwarz ...
James B
1
Als Mark Seeman oben traurig "Es kann gefährlich für Ihre Karriere sein" ...
Robert
Ich hatte keine Ahnung, dass Hintergrunderzählungen hier so ausdrucksstark sein könnten: P
Anurag
@ Robert Ich versuche das Zitat zu finden, wo oben hat er das gesagt?
Liang
8

Wenn Sie DI ohne IOC-Container verwenden, ist der größte Nachteil, dass Sie schnell erkennen, wie viele Abhängigkeiten Ihr Code tatsächlich aufweist und wie eng alles wirklich gekoppelt ist. ("Aber ich dachte, es wäre ein gutes Design!") Der natürliche Fortschritt besteht darin, sich einem IOC-Container zuzuwenden, dessen Erlernen und Implementieren etwas Zeit in Anspruch nehmen kann (nicht annähernd so schlecht wie die WPF-Lernkurve, aber nicht kostenlos entweder). Der letzte Nachteil ist, dass einige Entwickler anfangen werden, ehrliche Unit-Tests zu schreiben, und dass sie Zeit brauchen werden, um dies herauszufinden. Entwickler, die zuvor in einem halben Tag etwas herausholen konnten, werden plötzlich zwei Tage damit verbringen, herauszufinden, wie sie all ihre Abhängigkeiten verspotten können.

Ähnlich wie bei Mark Seemanns Antwort ist das Fazit, dass Sie Zeit damit verbringen, ein besserer Entwickler zu werden, anstatt Code-Teile zusammen zu hacken und sie aus der Tür / in die Produktion zu werfen. Welches hätte Ihr Unternehmen lieber? Nur Sie können das beantworten.

Mike Post
quelle
Guter Punkt. Das ist schlechte Qualität / schnelle Lieferung gegen gute Qualität / langsame Lieferung. Der Nachteil? DI ist keine Magie, es zu erwarten ist der Nachteil.
Liang
5

DI ist eine Technik oder ein Muster und steht in keinem Zusammenhang mit einem Framework. Sie können Ihre Abhängigkeiten manuell verkabeln. DI hilft Ihnen bei SR (Einzelverantwortung) und SoC (Trennung von Bedenken). DI führt zu einem besseren Design. Aus meiner Sicht und Erfahrung gibt es keine Nachteile . Wie bei jedem anderen Muster kann man es falsch verstehen oder missbrauchen (aber was ist im Fall von DI ziemlich schwer).

Wenn Sie DI als Prinzip in eine Legacy-Anwendung einführen und ein Framework verwenden, besteht der größte Fehler darin, es als Service-Locater zu missbrauchen. DI + Framework selbst ist großartig und hat die Dinge überall dort besser gemacht, wo ich es gesehen habe! Aus organisatorischer Sicht gibt es die häufigsten Probleme bei jedem neuen Prozess, jeder neuen Technik, jedem neuen Muster, ...:

  • Du musst dein Team trainieren
  • Sie müssen Ihre Anwendung ändern (einschließlich Risiken).

Im Allgemeinen muss man Zeit und Geld investieren , außerdem gibt es wirklich keine Nachteile!

Robert
quelle
3

Lesbarkeit des Codes. Sie können den Codefluss nicht einfach herausfinden, da die Abhängigkeiten in XML-Dateien versteckt sind.

Rahul
quelle
9
Sie müssen keine XML-Konfigurationsdateien für die Abhängigkeitsinjektion verwenden. Wählen Sie einen DI-Container, der die Konfiguration im Code unterstützt.
Håvard S
8
Die Abhängigkeitsinjektion impliziert nicht unbedingt XML-Dateien. Es scheint, dass dies in Java immer noch der Fall ist, aber in .NET haben wir diese Kopplung vor Jahren aufgegeben.
Mark Seemann
6
Oder verwenden Sie überhaupt keinen DI-Container.
Jesse Millikan
Wenn Sie wissen, dass der Code DI verwendet, können Sie leicht annehmen, wer die Abhängigkeiten festlegt.
Bozho
In Java benötigt Google Guice beispielsweise keine XML-Dateien.
Epaga
2

Zwei Dinge:

  • Sie benötigen zusätzliche Toolunterstützung, um die Gültigkeit der Konfiguration zu überprüfen.

Beispielsweise unterstützt IntelliJ (Commercial Edition) die Überprüfung der Gültigkeit einer Spring-Konfiguration und zeigt Fehler wie Typverletzungen in der Konfiguration an. Ohne diese Art der Toolunterstützung können Sie nicht überprüfen, ob die Konfiguration gültig ist, bevor Sie Tests ausführen.

Dies ist ein Grund, warum das Kuchenmuster (wie es der Scala-Community bekannt ist) eine gute Idee ist: Die Verkabelung zwischen Komponenten kann vom Typprüfer überprüft werden. Sie haben diesen Vorteil nicht mit Anmerkungen oder XML.

  • Dies macht die globale statische Analyse von Programmen sehr schwierig.

Frameworks wie Spring oder Guice machen es schwierig, statisch zu bestimmen, wie das vom Container erstellte Objektdiagramm aussehen wird. Obwohl sie beim Starten des Containers ein Objektdiagramm erstellen, bieten sie keine nützlichen APIs, die das Objektdiagramm beschreiben, das / würde / erstellt werden würde.

Martin Ellis
quelle
1
Das ist absoluter Bulle. Die Abhängigkeitsinjektion ist ein Konzept, für das kein Fricken-Framework erforderlich ist. Alles was Sie brauchen ist neu. Ditch Spring und all dieser Mist, dann funktionieren Ihre Tools einwandfrei und Sie können Ihren Code viel besser umgestalten.
time4tea
Richtig, guter Punkt. Ich hätte klarer sagen sollen, dass ich über Probleme mit DI-Frameworks und nicht über das Muster sprach. Es ist möglich, dass ich die Klärung der Frage bei der Beantwortung verpasst habe (vorausgesetzt, sie war zu diesem Zeitpunkt vorhanden).
Martin Ellis
Ah. In diesem Fall ziehe ich meinen Ärger glücklich zurück. Entschuldigung.
time4tea
2

Es scheint, als würden die vermeintlichen Vorteile einer statisch typisierten Sprache erheblich abnehmen, wenn Sie ständig Techniken anwenden, um die statische Typisierung zu umgehen. Ein großer Java-Shop, den ich gerade interviewt habe, war das Zuordnen ihrer Build-Abhängigkeiten mit statischer Code-Analyse ... die alle Spring-Dateien analysieren musste, um effektiv zu sein.

Chris D.
quelle
1

Dies kann die Startzeit der App verlängern, da der IoC-Container Abhängigkeiten ordnungsgemäß auflösen sollte und manchmal mehrere Iterationen erforderlich sind.

römisch
quelle
3
Um nur eine Zahl zu nennen, sollte ein DI-Container mehrere tausend Abhängigkeiten pro Sekunde auflösen. ( codinginstinct.com/2008/05/… ) DI-Container ermöglichen eine verzögerte Instanziierung. Die Leistung sollte (auch in großen Anwendungen) kein Problem sein. Zumindest sollte ein Leistungsproblem behoben werden können und kein Grund sein, sich gegen IoC und seine Frameworks zu entscheiden.
Robert