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.