Wann ist es nicht angebracht, das Abhängigkeitsinjektionsmuster zu verwenden?


124

Seitdem ich automatisiertes Testen gelernt (und geliebt) habe, habe ich festgestellt, dass ich das Abhängigkeitsinjektionsmuster in fast jedem Projekt verwendet habe. Ist es immer angebracht, dieses Muster zu verwenden, wenn Sie mit automatisierten Tests arbeiten? Gibt es Situationen, in denen Sie die Abhängigkeitsinjektion vermeiden sollten?


1
Related (wahrscheinlich nicht duplizieren) - stackoverflow.com/questions/2407540/…
Steve314

3
Klingt ein bisschen nach dem Golden Hammer-Anti-Pattern. en.wikipedia.org/wiki/Anti-pattern
Cuga

Antworten:


123

Grundsätzlich macht die Abhängigkeitsinjektion einige (normalerweise, aber nicht immer gültige) Annahmen über die Art Ihrer Objekte. Wenn diese falsch sind, ist DI möglicherweise nicht die beste Lösung:

  • Erstens geht DI grundsätzlich davon aus, dass eine enge Kopplung von Objektimplementierungen IMMER schlecht ist . Dies ist die Essenz des Abhängigkeitsumkehrprinzips: "Eine Abhängigkeit sollte niemals von einer Konkretion, sondern nur von einer Abstraktion hergestellt werden."

    Dadurch wird das abhängige zu ändernde Objekt aufgrund einer Änderung an der konkreten Implementierung geschlossen. Eine von ConsoleWriter abhängige Klasse muss sich ändern, wenn die Ausgabe stattdessen in eine Datei erfolgen soll. Wenn die Klasse jedoch nur von einem IWriter abhängt, der eine Write () -Methode verfügbar macht, können wir den derzeit verwendeten ConsoleWriter durch einen FileWriter und unseren ersetzen abhängige Klasse würde den Unterschied nicht kennen (Liskhov Substitution Principle).

    Ein Design kann jedoch NIEMALS für alle Arten von Änderungen geschlossen werden. Wenn sich das Design der IWriter-Schnittstelle selbst ändert, um Write () einen Parameter hinzuzufügen, muss jetzt ein zusätzliches Codeobjekt (die IWriter-Schnittstelle) über das Implementierungsobjekt / die Implementierungsmethode und deren Verwendung (en) geändert werden. Wenn Änderungen an der tatsächlichen Schnittstelle wahrscheinlicher sind als Änderungen an der Implementierung der Schnittstelle, kann eine lose Kopplung (und das Entstehen lose gekoppelter Abhängigkeiten) mehr Probleme verursachen, als sie löst.

  • Zweitens geht DI davon aus, dass die abhängige Klasse NIEMALS ein guter Ort ist, um eine Abhängigkeit zu erstellen . Dies gilt für das Prinzip der einheitlichen Verantwortung. Wenn Sie Code haben, der eine Abhängigkeit erstellt und auch verwendet, kann es zwei Gründe geben, die die abhängige Klasse ändern muss (eine Änderung der Verwendung ODER der Implementierung), wodurch die SRP verletzt wird.

    Das Hinzufügen von Indirektionsebenen für DI kann jedoch auch eine Lösung für ein nicht vorhandenes Problem sein. Wenn es logisch ist, Logik in eine Abhängigkeit zu kapseln, diese Logik jedoch die einzige derartige Implementierung einer Abhängigkeit ist, ist es schmerzhafter, die lose gekoppelte Auflösung der Abhängigkeit (Einspeisung, Servicestandort, Fabrik) zu codieren, als dies der Fall wäre einfach benutzen newund vergessen.

  • Schließlich zentralisiert DI von Natur aus das Wissen über alle Abhängigkeiten UND ihre Implementierungen . Dies erhöht die Anzahl der Referenzen, die die Assembly haben muss, die die Injektion durchführt, und verringert in den meisten Fällen NICHT die Anzahl der Referenzen, die von den Assemblys der tatsächlich abhängigen Klassen benötigt werden.

    Irgendetwas muss Kenntnis über die abhängige Schnittstelle, die Abhängigkeitsschnittstelle und die Abhängigkeitsimplementierung haben, um "die Punkte zu verbinden" und diese Abhängigkeit zu erfüllen. DI neigt dazu, all dieses Wissen auf einer sehr hohen Ebene zu platzieren, entweder in einem IoC-Container oder in dem Code, der "Haupt" -Objekte wie das Hauptformular oder den Controller erstellt, die die Abhängigkeiten hydratisieren müssen (oder Factory-Methoden dafür bereitstellen müssen). Dies kann eine Menge notwendigerweise eng gekoppelten Codes und eine Menge Assembler-Referenzen auf hohen Ebenen Ihrer App platzieren, die nur dieses Wissen benötigen, um es vor den tatsächlich abhängigen Klassen zu "verbergen" (was aus einer sehr grundlegenden Perspektive das ist) der beste Ort, um dieses Wissen zu haben (wo es verwendet wird).

    Normalerweise werden die Verweise auch nicht aus dem unteren Code-Bereich entfernt. Ein Abhängiger muss weiterhin auf die Bibliothek verweisen, die die Schnittstelle für seine Abhängigkeit enthält. Diese befindet sich an einer von drei Stellen:

    • Alles in einer einzigen "Interfaces" -Baugruppe, die sehr anwendungsorientiert wird.
    • jeweils neben der (den) primären Implementierung (en), wodurch der Vorteil beseitigt wird, dass Abhängigkeiten nicht neu kompiliert werden müssen, wenn sich Abhängigkeiten ändern, oder
    • Ein oder zwei Teile pro Baugruppe in Baugruppen mit hohem Zusammenhalt, wodurch die Baugruppenzahl aufgebläht wird, verlängern die "Full Build" -Zeiten erheblich und verringern die Anwendungsleistung.

    All dies, um ein Problem an Orten zu lösen, an denen es möglicherweise keine gibt.


1
SRP = Prinzip der Einzelverantwortung, für alle anderen, die sich fragen.
Theodore Murdock

1
Ja, und LSP ist das Liskov-Substitutionsprinzip. bei einer abhängigkeit von a sollte die abhängigkeit von b erfüllt werden können, das sich ohne weitere änderung von a ableitet.
KeithS

Die Abhängigkeitsinjektion hilft speziell dort, wo Sie Klasse A von Klasse B von Klasse D usw. abrufen müssen. In diesem Fall (und nur in diesem Fall) kann das DI-Framework weniger Aufblähung beim Zusammenbau verursachen als die Man-Injection von Poor's Man. Außerdem hatte ich nie einen Engpass, der durch DI verursacht wurde. Denken Sie an die Wartungskosten, niemals an die CPU-Kosten, da Code immer optimiert werden kann, aber das ohne Grund zu tun hat Kosten
GameDeveloper

Wäre eine andere Annahme "Wenn sich eine Abhängigkeit ändert, ändert sie sich überall"? Oder Sie müssen sich sowieso alle konsumierenden Klassen ansehen
Richard Tingle

3
Diese Antwort geht nicht auf die Auswirkungen von Abhängigkeiten auf die Testbarkeit ein. Siehe auch das Prinzip der expliziten Abhängigkeiten ( deviq.com/explicit-dependencies-principle )
ssmith

30

Außerhalb von Abhängigkeitsinjektions-Frameworks ist die Abhängigkeitsinjektion (über Konstruktorinjektion oder Setterinjektion) fast ein Nullsummenspiel: Sie verringern die Kopplung zwischen Objekt A und seiner Abhängigkeit B, aber jetzt muss jedes Objekt, das eine Instanz von A benötigt, eine Instanz von A sein konstruiere auch Objekt B.

Sie haben die Kopplung zwischen A und B geringfügig reduziert, aber die Einkapselung von A verringert und die Kopplung zwischen A und jeder Klasse, die eine Instanz von A erstellen muss, erhöht, indem Sie sie auch an die Abhängigkeiten von A koppeln.

Die Abhängigkeitsinjektion (ohne Framework) ist also ungefähr ebenso schädlich wie hilfreich.

Die zusätzlichen Kosten sind jedoch häufig leicht zu rechtfertigen: Wenn der Client-Code mehr über die Erstellung der Abhängigkeit als das Objekt selbst weiß, verringert die Abhängigkeitsinjektion die Kopplung tatsächlich. Ein Scanner weiß beispielsweise nicht viel darüber, wie ein Eingabestream abgerufen oder erstellt wird, von dem Eingaben analysiert werden sollen, oder von welcher Quelle der Client-Code Eingaben analysieren soll. Daher ist die Konstruktorinjektion eines Eingabestreams die naheliegende Lösung.

Testen ist eine weitere Rechtfertigung, um Scheinabhängigkeiten verwenden zu können. Das bedeutet, dass Sie einen zusätzlichen Konstruktor hinzufügen, der nur zum Testen verwendet wird und das Einfügen von Abhängigkeiten ermöglicht: Wenn Sie stattdessen Ihre Konstruktoren so ändern, dass immer Abhängigkeiten eingefügt werden müssen, müssen Sie plötzlich die Abhängigkeiten Ihrer Abhängigkeiten kennen, um die Abhängigkeiten zu konstruieren direkte Abhängigkeiten, und Sie können keine Arbeit erledigen.

Es kann hilfreich sein, aber Sie sollten sich auf jeden Fall fragen, ob der Testvorteil die Kosten wert ist und ob ich diese Abhängigkeit beim Testen wirklich verspotten möchte.

Wenn ein Abhängigkeitsinjektionsframework hinzugefügt und die Erstellung von Abhängigkeiten nicht an den Clientcode, sondern an das Framework delegiert wird, ändert sich die Kosten-Nutzen-Analyse erheblich.

In einem Abhängigkeitsinjektions-Framework sind die Kompromisse etwas unterschiedlich. Wenn Sie eine Abhängigkeit injizieren, verlieren Sie die Fähigkeit, leicht zu erkennen, auf welche Implementierung Sie sich verlassen, und die Verantwortung für die Entscheidung, auf welche Abhängigkeit Sie sich verlassen, auf einen automatisierten Lösungsprozess zu verlagern (z. B. wenn wir einen @ Inject'ed Foo benötigen) Es muss etwas geben, das @Provides Foo bereitstellt und dessen eingebundene Abhängigkeiten verfügbar sind, oder eine Konfigurationsdatei auf hoher Ebene, die angibt, welcher Anbieter für jede Ressource verwendet werden soll, oder eine Mischung aus beidem (z. B.) ein automatisierter Lösungsprozess für Abhängigkeiten sein, der bei Bedarf mithilfe einer Konfigurationsdatei überschrieben werden kann).

Wie bei der Konstruktorinjektion ist der Vorteil auch hier sehr ähnlich zu den Kosten: Sie müssen nicht wissen, wer die Daten bereitstellt, auf die Sie sich verlassen, und ob es mehrere Potenziale gibt Anbieter müssen Sie nicht die bevorzugte Reihenfolge kennen, um nach Anbietern zu suchen. Stellen Sie sicher, dass jeder Standort, an dem die Daten benötigt werden, nach allen potenziellen Anbietern usw. sucht, da all dies von der Abhängigkeitsinjektion auf hohem Niveau behandelt wird Plattform.

Ich persönlich habe nicht viel Erfahrung mit DI-Frameworks, aber ich habe den Eindruck, dass sie mehr Nutzen als Kosten bringen, wenn die Kopfschmerzen bei der Suche nach dem richtigen Anbieter der von Ihnen benötigten Daten oder Dienste höher sind als die Kopfschmerzen. Wenn etwas fehlschlägt, müssen Sie nicht sofort lokal wissen, welcher Code die fehlerhaften Daten geliefert hat, die einen späteren Fehler in Ihrem Code verursacht haben.

In einigen Fällen wurden andere Muster, die Abhängigkeiten verdecken (z. B. Service Locators), bereits übernommen (und haben sich möglicherweise auch bewährt), als DI-Frameworks auf dem Markt erschienen, und die DI-Frameworks wurden übernommen, weil sie einen Wettbewerbsvorteil boten, z. B. das Erfordernis weniger Boilerplate-Code oder möglicherweise weniger, um den Abhängigkeitsanbieter zu verdecken, wenn ermittelt werden muss, welcher Anbieter tatsächlich verwendet wird.


6
Nur ein kurzer Kommentar zu den in Tests verspotteten Abhängigkeiten und "Sie müssen die Abhängigkeiten Ihrer Abhängigkeiten kennen" ... Das stimmt überhaupt nicht. Man muss die Abhängigkeiten einiger konkreter Implementierungen nicht kennen oder sich nicht darum kümmern, wenn ein Mock eingespritzt wird. Man muss nur über die direkten Abhängigkeiten der zu testenden Klasse Bescheid wissen , die von den Mock (s) erfüllt werden.
Eric King

6
Sie verstehen es falsch, ich spreche nicht darüber, wenn Sie einen Schein einschleusen, ich spreche über den wirklichen Code. Betrachten Sie Klasse A mit Abhängigkeit B, die wiederum Abhängigkeit C hat, die wiederum Abhängigkeit D hat. Ohne DI konstruiert A B, B konstruiert C, C konstruiert D. Mit Konstruktionsinjektion müssen Sie zuerst B konstruieren, um A zu konstruieren. Um B zu konstruieren, müssen Sie zuerst C konstruieren, und um C zu konstruieren, müssen Sie zuerst D konstruieren. Klasse A muss nun über D, die Abhängigkeit einer Abhängigkeit von einer Abhängigkeit, Bescheid wissen, um B zu konstruieren. Dies führt zu übermäßiger Kopplung.
Theodore Murdock

1
Es würde nicht so viel kosten, einen zusätzlichen Konstruktor zum Testen zu haben, der nur das Einfügen der Abhängigkeiten ermöglicht. Ich werde versuchen zu überarbeiten, was ich gesagt habe.
Theodore Murdock

3
Die Abhängigkeitsinjektionsdosis bewegt nur Abhängigkeiten in einem Nullsummenspiel. Sie verschieben Ihre Abhängigkeiten an Orte, an denen sie bereits existieren: das Hauptprojekt. Sie können sogar konventionelle Ansätze verwenden, um sicherzustellen, dass die Abhängigkeiten nur zur Laufzeit aufgelöst werden, indem Sie beispielsweise angeben, welche Klassen durch Namespaces oder was auch immer konkret sind. Ich muss sagen, das ist eine naive Antwort
Sprague

2
@Sprague Dependency Injection bewegt sich in einem leicht negativen Summenspiel um Abhängigkeiten herum, wenn Sie es in Fällen anwenden, in denen es nicht angewendet werden sollte. Der Zweck dieser Antwort ist es, die Leute daran zu erinnern, dass die Abhängigkeitsinjektion nicht von Natur aus gut ist. Es ist ein Entwurfsmuster, das unter den richtigen Umständen unglaublich nützlich ist, aber unter normalen Umständen einen ungerechtfertigten Preis verursacht (zumindest in den Programmierbereichen, in denen ich gearbeitet habe) Ich verstehe, dass es in einigen Bereichen häufiger nützlich ist als in anderen. DI wird manchmal zu einem Modewort und zu einem Selbstzweck, der den Sinn verfehlt.
Theodore Murdock

11
  1. Wenn Sie Datenbankentitäten erstellen, sollten Sie lieber eine Factory-Klasse haben, die Sie stattdessen in Ihren Controller einfügen.

  2. wenn Sie primitive Objekte wie Ints oder Longs erstellen müssen. Außerdem sollten Sie die meisten Standardbibliotheksobjekte wie Daten, Anleitungen usw. "von Hand" erstellen.

  3. Wenn Sie Konfigurationszeichenfolgen einfügen möchten, ist es wahrscheinlich besser, einige Konfigurationsobjekte einzufügen (im Allgemeinen wird empfohlen, einfache Typen in aussagekräftige Objekte einzufügen: int temperatureInCelsiusDegrees -> CelciusDeegree temperature).

Und verwenden Sie den Service Locator nicht als Alternative zur Abhängigkeitsinjektion, er ist ein Anti-Pattern. Weitere Informationen: http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx


In allen Punkten geht es darum, eine andere Form der Injektion zu verwenden, anstatt sie insgesamt nicht zu verwenden. +1 für den Link.
Jan Hudec

3
Service Locator ist KEIN Anti-Pattern und der Typ, mit dem Sie einen Blog verlinkt haben, ist kein Experte für Patterns. Dass er durch StackExchange berühmt wurde, ist ein Nachteil für das Software-Design. Martin Fowler (Autor von Staple Patterns of Enterprise Application Architecture) hat eine vernünftigere Sicht auf die Angelegenheit: martinfowler.com/articles/injection.html
Colin

1
@Colin, im Gegenteil, Service Locator ist definitiv ein Anti-Pattern, und hier in aller Kürze der Grund dafür .
Kyralessa

@Kyralessa - Sie stellen fest, dass DI-Frameworks intern ein Service Locator-Muster verwenden, um nach Services zu suchen, oder? Es gibt Umstände, in denen beides angemessen ist, und trotz des Hasses, den manche Leute gegen StackExchange hegen, ist keines von beiden ein Anti-Pattern.
Colin

Klar, und mein Auto verwendet intern Kolben und Zündkerzen und eine Menge anderer Dinge, die jedoch keine gute Schnittstelle für einen gewöhnlichen Menschen sind, der nur ein Auto fahren möchte.
Kyralessa

8

Wenn Sie nichts gewinnen können, indem Sie Ihr Projekt wartbar und testbar machen.

Im Ernst, ich liebe IoC und DI im Allgemeinen, und ich würde sagen, dass ich dieses Muster in 98% der Fälle unbedingt verwenden werde. Dies ist besonders wichtig in einer Mehrbenutzerumgebung, in der Sie Code immer wieder von verschiedenen Teammitgliedern und verschiedenen Projekten wiederverwenden können, da die Logik von der Implementierung getrennt wird. Die Protokollierung ist ein Paradebeispiel dafür. Eine in eine Klasse injizierte ILog-Schnittstelle ist tausendmal wartbarer als das einfache Einstecken Ihres Protokollierungsframeworks, da Sie nicht garantieren können, dass ein anderes Projekt dasselbe Protokollierungsframework verwendet (sofern es verwendet) eine überhaupt!).

Es gibt jedoch Zeiten, in denen es sich nicht um ein anwendbares Muster handelt. Beispielsweise können funktionale Einstiegspunkte, die in einem statischen Kontext von einem nicht überschreibbaren Initialisierer implementiert werden (WebMethods, ich sehe Sie, aber Ihre Main () - Methode in Ihrer Program-Klasse ist ein anderes Beispiel), bei der Initialisierung einfach keine Abhängigkeiten enthalten Zeit. Ich würde auch so weit gehen zu sagen, dass ein Prototyp oder ein Wegwerf-Code ebenfalls ein schlechter Kandidat ist. Die Vorteile von DI sind im Wesentlichen mittel- bis langfristige Vorteile (Testbarkeit und Wartbarkeit), wenn Sie sicher sind, dass Sie den größten Teil eines Codeteils innerhalb einer Woche wegwerfen werden, würde ich sagen, dass Sie durch das Isolieren nichts gewinnen Abhängigkeiten, verbringen Sie nur die Zeit, die Sie normalerweise damit verbringen würden, Abhängigkeiten zu testen und zu isolieren, um den Code zum Laufen zu bringen.

Alles in allem ist es sinnvoll, eine Methode oder ein Muster pragmatisch zu betrachten, da nichts zu 100% anwendbar ist.

Zu beachten ist Ihr Kommentar zu automatisierten Tests: Meine Definition hierfür sind automatisierte Funktionstests , z. B. Skript-Selentests, wenn Sie sich in einem Webkontext befinden. Hierbei handelt es sich in der Regel um vollständige Black-Box-Tests, bei denen die Funktionsweise des Codes nicht bekannt sein muss. Wenn Sie sich auf Unit- oder Integrationstests beziehen, würde ich sagen, dass das DI-Muster fast immer auf jedes Projekt anwendbar ist, das sich stark auf diese Art von White-Box-Tests stützt, da es Ihnen beispielsweise ermöglicht, Dinge wie zu testen Methoden, die die Datenbank berühren, ohne dass eine Datenbank vorhanden sein muss.


Ich verstehe nicht, was du mit Protokollierung meinst. Sicherlich werden nicht alle Logger der gleichen Schnittstelle entsprechen, sodass Sie Ihren eigenen Wrapper schreiben müssen, um zwischen der Protokollierungsmethode dieses Projekts und einem bestimmten Logger zu übersetzen (vorausgesetzt, Sie möchten in der Lage sein, die Logger einfach zu ändern). Also, was gibt dir DI danach?
Richard Tingle

@RichardTingle Ich würde sagen, Sie sollten Ihren Logging-Wrapper als Schnittstelle definieren und dann eine einfache Implementierung dieser Schnittstelle für jeden externen Logging-Service schreiben, anstatt eine einzelne Logging-Klasse zu verwenden, die mehrere Abstraktionen umschließt. Es ist dasselbe Konzept, das Sie vorschlagen, aber auf einer anderen Ebene abstrahiert.
Ed James

1

Während sich die anderen Antworten auf die technischen Aspekte konzentrieren, möchte ich eine praktische Dimension hinzufügen.

Im Laufe der Jahre bin ich zu dem Schluss gekommen, dass mehrere praktische Anforderungen erfüllt werden müssen, damit die Einführung von Dependency Injection erfolgreich sein kann.

  1. Es sollte einen Grund geben, es einzuführen.

    Das klingt offensichtlich, aber wenn Ihr Code nur Dinge aus der Datenbank erhält und diese ohne Logik zurückgibt, macht das Hinzufügen eines DI-Containers die Dinge komplexer, ohne dass ein wirklicher Nutzen daraus entsteht. Wichtiger wäre hier das Testen der Integration.

  2. Das Team muss geschult und an Bord sein.

    Es sei denn, die Mehrheit des Teams ist an Bord und weiß, dass das Hinzufügen der Inversion des Kontrollcontainers eine weitere Möglichkeit ist, Dinge zu tun, und die Codebasis noch komplizierter macht.

    Wenn der DI von einem neuen Mitglied des Teams eingeführt wird, weil sie ihn verstehen und mögen und nur zeigen wollen, dass sie gut sind, UND das Team nicht aktiv beteiligt ist, besteht das reale Risiko, dass er tatsächlich die Qualität von mindert der Code.

  3. Sie müssen testen

    Während das Entkoppeln im Allgemeinen eine gute Sache ist, kann DI die Auflösung einer Abhängigkeit von der Kompilierungszeit zur Laufzeit verschieben. Dies ist eigentlich ziemlich gefährlich, wenn Sie nicht gut testen. Fehler bei der Laufzeitauflösung können kostspielig sein, um sie zu verfolgen und zu beheben.
    (Aus Ihrem Test geht hervor, dass Sie dies tun, aber viele Teams testen nicht in dem von DI geforderten Umfang.)


1

Dies ist keine vollständige Antwort, sondern nur ein weiterer Punkt.

Wenn Sie eine Anwendung haben, die einmal gestartet wurde, wird sie möglicherweise über einen längeren Zeitraum ausgeführt (wie eine Webanwendung). DI ist möglicherweise hilfreich.

Wenn Sie eine Anwendung haben, die häufig gestartet und kürzer ausgeführt wird (wie eine mobile Anwendung), möchten Sie den Container wahrscheinlich nicht.


Ich kann nicht sehen, wie sich die Live-Zeit der Bewerbung auf DI
Michael Freidgeim

@MichaelFreidgeim Die Initialisierung des Kontexts dauert einige Zeit. Normalerweise sind DI-Container ziemlich schwer, wie z. B. Spring. Erstelle eine hallo Welt App mit nur einer Klasse und mach eine mit Spring und starte beide 10 Mal und du wirst sehen, was ich meine.
Koray Tugay

Es klingt wie ein Problem mit einem einzelnen DI-Container. In der .Net-Welt habe ich noch nichts über die Initialisierungszeit als wesentliches Problem für DI-Container gehört
Michael Freidgeim,

1

Versuchen Sie, grundlegende OOP-Prinzipien zu verwenden: Verwenden Sie die Vererbung , um allgemeine Funktionen zu extrahieren, Dinge zu kapseln (verbergen), die durch private / interne / geschützte Mitglieder / Typen vor der Außenwelt geschützt werden sollen. Verwenden Sie ein leistungsfähiges Test-Framework, um Code nur für Tests einzufügen, z. B. https://www.typemock.com/ oder https://www.telerik.com/products/mocking.aspx .

Dann versuchen neu zu schreiben es mit DI und vergleicht Code, was Sie in der Regel mit DI sehen:

  1. Sie haben mehr Schnittstellen (mehr Typen)
  2. Sie haben Duplikate von Signaturen öffentlicher Methoden erstellt und müssen diese doppelt verwalten (Sie können einen Parameter nicht einfach einmal ändern, sondern müssen ihn zweimal ändern, da im Grunde alle Refactoring- und Navigationsmöglichkeiten komplizierter werden).
  3. Sie haben einige Kompilierungsfehler verschoben, um Zeitfehler auszuführen (mit DI können Sie einige Abhängigkeiten während des Codierens einfach ignorieren und sind nicht sicher, ob sie während des Testens verfügbar sind).
  4. Sie haben Ihre Kapselung geöffnet. Jetzt wurden geschützte Mitglieder, interne Typen usw. öffentlich
  5. Die Gesamtmenge des Codes wurde erhöht

Ich würde sagen, nach allem, was ich gesehen habe, nimmt die Codequalität mit DI fast immer ab.

Wenn Sie jedoch in der Klassendeklaration nur den Zugriffsmodifikator "public" und / oder den Modifikator "public / private" für Mitglieder verwenden und / oder keine Wahl haben, teure Testtools zu kaufen, und gleichzeitig einen Komponententest benötigen, der Folgendes kann: t durch integrative Tests ersetzt werden, und / oder Sie haben bereits Schnittstellen für Klassen, die Sie zu injizieren denken, DI ist eine gute Wahl!

ps wahrscheinlich bekomme ich viele Minuspunkte für diesen Post, ich glaube, weil die meisten modernen Entwickler einfach nicht verstehen, wie und warum man interne Schlüsselwörter verwendet und wie man die Kopplung Ihrer Komponenten verringert und warum man sie schließlich verringert) versuche zu codieren und zu vergleichen


0

Eine Alternative zu Dependency Injection ist die Verwendung eines Service Locator . Ein Service Locator ist einfacher zu verstehen, zu debuggen und vereinfacht das Erstellen eines Objekts, insbesondere wenn Sie kein DI-Framework verwenden. Service Locators sind ein gutes Muster für die Verwaltung externer statischer Abhängigkeiten , z. B. einer Datenbank, die Sie ansonsten an jedes Objekt in Ihrer Datenzugriffsebene übergeben müssten.

Beim Refactoring von Legacy-Code ist es häufig einfacher, eine Refactoring-Funktion für einen Service-Locator auszuführen als für die Abhängigkeitsinjektion. Alles, was Sie tun, ist, Instantiierungen durch Service-Lookups zu ersetzen und dann den Service in Ihrem Komponententest herauszufälschen.

Der Service Locator hat jedoch einige Nachteile . Das Erkennen der Abhängigkeiten einer Klasse ist schwieriger, da die Abhängigkeiten in der Implementierung der Klasse und nicht in Konstruktoren oder Setzern verborgen sind. Das Erstellen von zwei Objekten, für die unterschiedliche Implementierungen desselben Dienstes erforderlich sind, ist schwierig oder unmöglich.

TLDR : Wenn Ihre Klasse statische Abhängigkeiten aufweist oder Sie Legacy-Code überarbeiten , ist ein Service Locator wahrscheinlich besser als DI.



Wenn es um statische Abhängigkeiten geht, würde ich lieber Fassaden sehen, die Schnittstellen implementieren. Diese können dann Abhängigkeit injiziert werden, und sie fallen auf die statische Granate für Sie.
Erik Dietrich

@Kyralessa Ich stimme zu, ein Service Locator hat viele Nachteile und DI ist fast immer vorzuziehen. Ich glaube jedoch, dass es, wie bei allen Programmierungsprinzipien, ein paar Ausnahmen von der Regel gibt.
Garrett Hall

Die hauptsächliche akzeptierte Verwendung des Service-Standorts liegt in einem Strategiemuster, in dem einer "Strategie-Auswahl" -Klasse genügend Logik gegeben wird, um die zu verwendende Strategie zu finden und sie entweder zurückzugeben oder einen Anruf an sie weiterzuleiten. Auch in diesem Fall kann der Strategy Picker eine Fassade für eine Factory-Methode sein, die von einem IoC-Container bereitgestellt wird, der dieselbe Logik erhält. Der Grund, warum Sie es herausbrechen würden, ist, die Logik dort zu platzieren, wo sie hingehört und nicht dort, wo sie am meisten verborgen ist.
KeithS

Martin Fowler zitiert: "Die Wahl zwischen (DI und Service Locator) ist weniger wichtig als das Prinzip der Trennung von Konfiguration und Verwendung." Die Idee, dass DI IMMER besser ist, ist Hogwash. Wenn ein Dienst global verwendet wird, ist die Verwendung von DI umständlicher und spröder. Außerdem ist DI für Plugin-Architekturen ungünstiger, bei denen Implementierungen erst zur Laufzeit bekannt sind. Überlegen Sie, wie umständlich es wäre, immer zusätzliche Argumente an Debug.WriteLine () und dergleichen zu übergeben!
Colin
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.