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.
Antworten:
Ein paar Punkte:
Im Allgemeinen erleichtert der Vorteil der Entkopplung das Lesen und Verstehen jeder Aufgabe, erhöht jedoch die Komplexität der Orchestrierung der komplexeren Aufgaben.
quelle
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 ...
Ich habe eine Abhängigkeit, die ich extrahieren und injizieren möchte - den Text "Hello World". Leicht genug...
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.
quelle
Hier ist meine erste Reaktion: Grundsätzlich die gleichen Nachteile eines Musters.
quelle
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.
quelle
Ich glaube nicht, dass eine solche Liste existiert, aber versuchen Sie, diese Artikel zu lesen:
DI kann den Code verdecken (wenn Sie nicht mit einer guten IDE arbeiten)
Der Missbrauch von IoC kann laut Onkel Bob zu schlechtem Code führen.
Sie müssen auf Überentwicklung achten und unnötige Vielseitigkeit schaffen.
quelle
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:
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.
quelle
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.
quelle
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.
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.
quelle
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 ...... 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.quelle
this.errorLogger.WriteError(ex)
wenn in einer try / catch-Anweisung ein Fehler auftritt.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.
quelle
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.
quelle
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
doSomething
Methode 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.
quelle
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!)
quelle
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.
quelle
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, ...:
Im Allgemeinen muss man Zeit und Geld investieren , außerdem gibt es wirklich keine Nachteile!
quelle
Lesbarkeit des Codes. Sie können den Codefluss nicht einfach herausfinden, da die Abhängigkeiten in XML-Dateien versteckt sind.
quelle
Zwei Dinge:
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.
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.
quelle
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.
quelle
Dies kann die Startzeit der App verlängern, da der IoC-Container Abhängigkeiten ordnungsgemäß auflösen sollte und manchmal mehrere Iterationen erforderlich sind.
quelle