it-swarm.com.de

Was sind die Nachteile von Dependency Injection?

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

Hinweis Ich suche hier nach einer möglichst vollständigen Liste, nicht nach einer subjektiven Diskussion zum Thema.


Erläuterung : Ich spreche von der Abhängigkeitsinjektion Muster (siehe dieser Artikel von Martin Fowler), nicht ein spezifisches Framework, ob XML-basiert (wie Spring) oder Code-basiert (wie Guice) oder "Self-Rolled".


Bearbeiten : Einige großartige weitere Diskussionen/Rants/Debatten finden statt / r/programming hier.

332
Epaga

Ein paar Punkte:

  • DI erhöht die Komplexität, in der Regel durch Erhöhung der Anzahl der Klassen, da die Verantwortlichkeiten mehr voneinander getrennt sind, was nicht immer vorteilhaft ist
  • Ihr Code wird (in gewisser Weise) an das von Ihnen verwendete Abhängigkeitsinjektionsframework (oder allgemeiner an die Implementierung des DI-Musters) gekoppelt.
  • DI-Container oder -Ansätze, die eine Typauflösung durchführen, verursachen im Allgemeinen einen geringen Zeitverlust (sehr vernachlässigbar, aber vorhanden).

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

202
Håvard S

Das gleiche Grundproblem tritt häufig bei objektorientierter Programmierung, Stilregeln und so ziemlich allem anderen auf. Tatsächlich ist es sehr häufig möglich, zu viel Abstraktion zu betreiben, zu viel Indirektion hinzuzufügen und im Allgemeinen gute Techniken exzessiv und an den falschen Stellen anzuwenden.

Jedes Muster oder andere Konstrukt, das Sie anwenden, bringt Komplexität mit sich. Abstraktion und Indirektion streuen Informationen, schieben manchmal irrelevante Details aus dem Weg, erschweren es aber auch, 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 datenabhä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. Der Punkt hier ist, ein triviales Beispiel zu nehmen und die möglichen Probleme zu zeigen.

Nehmen wir diese einfache traditionelle Funktion - die C++ - Syntax spielt hier keine Rolle, 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. Das bedeutet aber, dass meine Strings 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 Wartungsarbeiten, die nicht nötig wären, wenn wir das Original behalten würden.

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

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

Das sind 12 "Abhängigkeiten". 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 gibt bereits eine ältere Version, die vermutlich hinter den Kulissen auf die neuere Version abgebildet 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, wie die Abhängigkeiten lauten, 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 prinzipiell nicht. Das Schreiben einer Klasse ist sowohl im Quelltext als auch in der Entwicklerzeit ein Overhead. Wenn diese Klasse so geschrieben wird, dass Abhängigkeiten gemäß einigen Spezifikationen für abhängige Objekte angegeben werden, muss das abhängige Objekt diese Schnittstelle auch dann unterstützen, wenn dies erforderlich ist 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 übertrieben 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.

180
Steve314

Hier ist meine eigene 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 es den Nutzen rechtfertigen würde
75
Epaga

Der größte "Nachteil" von Inversion of Control (nicht ganz DI, aber nahe genug) besteht darin, dass es sich nicht mehr darum handelt, einen einzigen Punkt zu haben, um einen Überblick über einen Algorithmus zu erhalten. Das ist im Grunde das, was passiert, wenn Sie Code entkoppelt haben - die Fähigkeit, an einer Stelle zu suchen, ist ein Artefakt der engen Kopplung.

45
kyoryu
41

Ich habe Guice (Java DI Framework) in den letzten 6 Monaten ausgiebig benutzt. Insgesamt finde ich es großartig (vor allem aus der Sicht des Testens), aber es gibt bestimmte Nachteile. Vor allem:

  • Code kann schwerer zu verstehen sein. Die Abhängigkeitsinjektion kann auf sehr ... kreative ... Weise verwendet werden. Zum Beispiel bin ich gerade auf einen Code gestoßen, der eine benutzerdefinierte Annotation zum Einfügen bestimmter IOStreams verwendete (z. B .: @ Server1Stream, @ Server2Stream). Dies funktioniert zwar und ich gebe zu, dass es eine gewisse Eleganz hat, aber das Verständnis der Guice-Injektionen ist eine Voraussetzung für das Verständnis des Codes.
  • Höhere Lernkurve beim Lernen des Projekts. 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 an meinem jetzigen Arbeitsplatz anfing, verbrachte ich einige verwirrte Stunden damit, darüber nachzudenken, was Guice hinter den Kulissen tat.
  • Konstruktoren werden groß. Obwohl dies mit einem Standardkonstruktor oder einer Factory weitgehend gelöst werden kann.
  • Fehler können verschleiert werden. Mein letztes Beispiel dafür war eine Kollision mit 2 Flaggennamen. Guice schluckte den Fehler und eine meiner Flaggen wurde nicht initialisiert.
  • Fehler werden zur Laufzeit übertragen. Wenn Sie Ihr Guice-Modul falsch konfigurieren (zirkulärer Verweis, 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 habe ich mich beschwert. Lassen Sie mich sagen, dass ich Guice weiterhin (bereitwillig) in meinem aktuellen und höchstwahrscheinlich meinem nächsten Projekt verwenden werde. Die Abhängigkeitsinjektion ist ein großartiges und unglaublich leistungsfähiges Muster. Aber es kann auf jeden Fall verwirrend sein und Sie werden mit ziemlicher Sicherheit einige Zeit damit verbringen, über das von Ihnen gewählte Abhängigkeitsinjektions-Framework zu verfluchen.

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

40
Andy Peck

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

Code mit häufig verwendetem DI kann Ravioli-Code sein, wobei jede kleine Klasse wie ein einzelnes Ravioli-Nugget ist - sie macht eine kleine Sache und das Prinzip der einmaligen Verantwortung wird eingehalten, was ist gut. Aber wenn man sich die Klassen selbst ansieht, ist es schwer zu erkennen, was das System als Ganzes leistet, da dies davon abhängt, wie all diese vielen kleinen Teile zusammenpassen, was schwer zu sehen ist. Es sieht aus wie ein großer Haufen kleiner Dinge.

Indem Sie die Spaghetti-Komplexität großer Teile gekoppelten Codes in einer großen Klasse vermeiden, riskieren Sie eine andere Komplexität, bei der es viele einfache kleine Klassen gibt und die Wechselwirkungen zwischen ihnen komplex sind.

Ich denke nicht, dass dies ein fataler Nachteil ist - DI ist immer noch sehr lohnenswert. Ein gewisser Grad 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. Zu wissen, dass es zu weit gehen kann, ist der erste Schritt, um dies zu vermeiden. Folgen Sie den Links, um zu erfahren, wie Sie dies vermeiden können.

24
Anthony

Wenn Sie eine selbst entwickelte Lösung haben, sind die Abhängigkeiten im Konstruktor genau richtig. Oder vielleicht als Methodenparameter, die wiederum nicht allzu schwer zu erkennen sind. Abhängigkeiten, die vom Framework verwaltet werden, können, wenn sie auf die Spitze getrieben werden, wie Magie erscheinen.

Zu viele Abhängigkeiten in zu vielen Klassen sind jedoch ein klares Zeichen dafür, dass die Klassenstruktur durcheinander ist. Auf eine Art und Weise kann die Abhängigkeitsinjektion (selbst entwickelt oder vom Framework verwaltet) dabei helfen, auffällige Designprobleme hervorzuheben, die andernfalls 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 es 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 alles selbst tun müssen. Sie können jedoch nicht die Gestaltung 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 dabei die Konsistenz und Harmonie des gesamten Designs beibehalten? 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.

13
Anurag

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

Dies kann von Bedeutung sein, wenn eine Abhängigkeit nicht häufig innerhalb einer konsumierenden Klasse verwendet wird. so etwas wie ein IExceptionLogHandlerService. Offensichtlich wird ein solcher Dienst (hoffentlich :) nur selten innerhalb der Klasse aufgerufen - vermutlich nur in Ausnahmefällen, die protokolliert werden müssen. doch das kanonisches 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 Service-/Datenbank-Treffer oder das Nachschlagen von Konfigurationsdateien oder das Sperren einer Ressource bis zur Entsorgung zur Folge hätte? Wenn dieser Dienst stattdessen nach Bedarf, am Ort des Dienstes oder im Werk erstellt wurde (alle haben ihre eigenen Probleme), würden Sie die Baukosten nur dann übernehmen, wenn dies erforderlich ist.

Nun ist es ein allgemein anerkanntes Prinzip des Software-Designs, dass das Konstruieren eines Objekts ist billig und nicht Nebenwirkungen hervorruft. Und obwohl dies eine nette Vorstellung ist, ist dies 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 implementiert wird, werden Sie wahrscheinlich gezwungen sein, dieses Design zu überdenken.

Übrigens können bestimmte Techniken dieses genaue Problem mildern, indem sie das verzögerte Laden von injizierten Abhängigkeiten ermöglichen, z. Bereitstellung einer Klasse a Lazy<IService> Instanz als Abhängigkeit. Dies würde die Konstruktoren Ihrer abhängigen Objekte ändern und dann Implementierungsdetails wie den Objektkonstruktionsaufwand noch deutlicher berücksichtigen, was ebenfalls nicht wünschenswert ist.

13
ckittel

Die Illusion, dass Sie Ihren Code einfach durch Implementieren der Abhängigkeitsinjektion entkoppelt haben, ohne ihn WIRKLICH zu entkoppeln. Ich denke, das ist das Gefährlichste an DI.

12
Joey Guerra

Das ist eher ein Trottel. Ein Nachteil der Abhängigkeitsinjektion ist jedoch, dass es für Entwicklungstools etwas schwieriger ist, über Code nachzudenken und darin zu navigieren.

Insbesondere 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 eine Kehrseite von lose gekoppeltem Code (Code, der von der Schnittstelle entworfen wurde) und gilt auch dann, wenn Sie keine Abhängigkeitsinjektion verwenden (d. H. Selbst wenn Sie einfach Fabriken verwenden). Aber das Aufkommen der Abhängigkeitsinjektion hat wirklich dazu beigetragen, dass Code locker an die Massen gekoppelt wurde, also dachte ich, ich würde es erwähnen.

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

Tatsächlich wage ich zu vermuten, dass für jeden "Nachteil", den Sie bei der Abhängigkeitsinjektion feststellen können, viele Vorteile auftreten, die diese bei weitem überwiegen.

11
Jack Leow

Konstruktorbasierte Abhängigkeitsinjektion (ohne die Hilfe von magischen "Frameworks") ist eine saubere und nützliche Möglichkeit, OO Code zu strukturieren. In den besten Codebasen, die ich gesehen habe und die ich über Jahre mit anderen Ex-Kollegen von Martin Fowler verbracht habe, habe ich festgestellt, dass die meisten guten Klassen, die auf diese Weise geschrieben wurden, eine einzige doSomething -Methode haben.

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

10
sanityinc

Ich finde, dass Konstruktor-Injection zu großen hässlichen Konstruktoren führen kann (und ich verwende es in meiner gesamten Codebasis - vielleicht sind meine Objekte zu granular?). Außerdem ende ich manchmal mit Konstruktor-Injection mit schrecklichen zirkulären Abhängigkeiten (obwohl dies sehr selten ist), so dass Sie möglicherweise eine Art Ready-State-Lifecycle mit mehreren Runden von Abhängigkeits-Injection in einem komplexeren System haben müssen.

Ich bevorzuge jedoch die Construtor-Injektion gegenüber der Setter-Injektion, da ich nach der Erstellung meines Objekts zweifelsfrei weiß, in welchem ​​Zustand es sich befindet, ob es sich in einer Unit-Test-Umgebung befindet oder in einer anderen geladen ist IOC = Container. Was auf eine Art und Weise aussagt, was ich für den Hauptnachteil der Setterinjektion halte.

(Als Nebenbemerkung finde ich das ganze Thema ziemlich "religiös", aber Ihre Meilen variieren mit dem technischen Eifer Ihres Entwicklerteams!)

9
James B

Wenn Sie DI ohne IOC -Container verwenden, ist der größte Nachteil, dass Sie schnell erkennen, wie viele Abhängigkeiten Ihr Code tatsächlich hat und wie eng alles wirklich gekoppelt ist. ("Aber ich dachte, es wäre ein gutes Design!") Der natürliche Fortschritt besteht darin, auf einen IOC -Container zuzugehen, dessen Erlernen und Implementieren einige Zeit in Anspruch nehmen kann (nicht annähernd so schlecht wie das WPF-Lernen) Kurve, aber es ist auch nicht frei). Der letzte Nachteil ist, dass einige Entwickler anfangen werden, ehrlich zu Güte-Unit-Tests zu schreiben, und es wird einige Zeit dauern, bis sie es herausgefunden haben. Entwickler, die früher an 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 Mark Seemanns Antwort lautet das Fazit, dass Sie Zeit damit verbringen, ein besserer Entwickler zu werden, anstatt Code-Teile zusammen zu hacken und aus der Tür/in die Produktion zu werfen. Welches würde Ihr Unternehmen lieber haben? Das können nur Sie beantworten.

8
Mike Post

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

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

  • Sie müssen Ihr Team trainieren
  • Sie müssen Ihre Bewerbung ändern (einschließlich Risiken)

Im Allgemeinen muss man Zeit und Geld investieren, daneben gibt es wirklich keine Nachteile!

5
Robert

Lesbarkeit des Codes. Sie werden den Code-Fluss nicht leicht herausfinden können, da die Abhängigkeiten in XML-Dateien verborgen sind.

3
Rahul

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

2
Chris D

Zwei Dinge:

  • Sie benötigen zusätzliche Toolunterstützung, um zu überprüfen, ob die Konfiguration gültig ist.

IntelliJ (Commercial Edition) unterstützt beispielsweise die Überprüfung der Gültigkeit einer Spring-Konfiguration und meldet Fehler wie z. B. Typverstöße in der Konfiguration. Ohne diese Art von 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 'Kuchen'-Muster (wie es der Scala= Community bekannt ist) eine gute Idee ist: Die Verdrahtung zwischen Komponenten kann durch die Typprüfung überprüft werden diese profitieren mit Anmerkungen oder XML.

  • Dies erschwert die globale statische Analyse von Programmen.

Frameworks wie Spring oder Guice erschweren die statische Bestimmung des vom Container erstellten Objektdiagramms. Obwohl sie beim Starten des Containers ein Objektdiagramm erstellen, bieten sie keine nützlichen APIs, die das Objektdiagramm beschreiben, das erstellt werden soll.

2
Martin Ellis

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

1
Roman