Grundlegendes zur Abhängigkeitsinjektion

109

Ich lese über Abhängigkeitsinjektion (DI). Für mich ist es eine sehr komplizierte Sache, da ich las, dass es sich auch um eine Inversion der Kontrolle (IoC) handelte und ich das Gefühl hatte, dass ich auf eine Reise gehen würde.

Dies ist mein Verständnis: Anstatt ein Modell in der Klasse zu erstellen, das es auch verwendet, übergeben Sie das Modell (das bereits mit interessanten Eigenschaften gefüllt ist) dort, wo es benötigt wird (an eine neue Klasse, in der es als Parameter verwendet werden könnte) der Konstruktor).

Für mich ist dies nur eine Auseinandersetzung. Ich muss den Punkt verpasst haben? Vielleicht wird es bei größeren Projekten offensichtlicher?

Mein Verständnis ist nicht-DI (mit Pseudo-Code):

public void Start()
{
    MyClass class = new MyClass();
}

...

public MyClass()
{
    this.MyInterface = new MyInterface(); 
}

Und DI wäre

public void Start()
{
    MyInterface myInterface = new MyInterface();
    MyClass class = new MyClass(myInterface);
}

...

public MyClass(MyInterface myInterface)
{
    this.MyInterface = myInterface; 
}

Könnte jemand etwas Licht ins Dunkel bringen, ich bin mir sicher, dass ich hier durcheinander bin.

MyDaftQuestions
quelle
5
Versuchen Sie, 5 der Klassen MyInterface und MyClass UND mehrere Klassen zu haben, die eine Abhängigkeitskette bilden. Es wird zum Schmerz im Hintern, alles einzurichten.
Euphorischer
159
" Für mich ist das nur eine Auseinandersetzung. Ich muss den Punkt falsch verstanden haben? " Du hast es. Das ist "Abhängigkeitsinjektion". Nun sehen Sie, welche andere verrückte Fachsprache Sie für einfache Konzepte finden können, es macht Spaß!
Eric Lippert
8
Ich kann die Bemerkung von Herrn Lippert nicht genug unterstützen. Ich fand es auch verwirrend Jargon für etwas, das ich natürlich sowieso tat.
Alex Humphrey
16
@EricLippert: Ich denke, das ist zwar richtig, aber ein bisschen reduzierend. Parameters-as-DI ist kein direktes Konzept für einen durchschnittlichen Imperativ- / OO-Programmierer, der es gewohnt ist, Daten weiterzugeben. DI Hier können Sie Verhaltensweisen weitergeben , die eine flexiblere Weltsicht erfordern als ein mittelmäßiger Programmierer.
Phoshi
14
@Phoshi: Du machst einen guten Punkt, aber du hast auch deinen Finger darauf gelegt, warum ich skeptisch bin, dass "Abhängigkeitsinjektion" überhaupt eine gute Idee ist. Wenn ich eine Klasse schreibe, deren Korrektheit und Leistung vom Verhalten einer anderen Klasse abhängt, möchte ich als Letztes den Benutzer der Klasse für die korrekte Erstellung und Konfiguration der Abhängigkeit verantwortlich machen! Das ist mein job Jedes Mal, wenn Sie einen Konsumenten eine Abhängigkeit injizieren lassen, erstellen Sie einen Punkt, an dem der Konsument es möglicherweise falsch macht.
Eric Lippert

Antworten:

106

Nun ja, Sie fügen Ihre Abhängigkeiten entweder über einen Konstruktor oder über Eigenschaften ein.
Einer der Gründe dafür ist, MyClass nicht mit den Details zu belasten, wie eine Instanz von MyInterface erstellt werden muss. MyInterface kann eine ganze Liste von Abhängigkeiten enthalten, und der Code von MyClass würde sehr schnell hässlich, wenn Sie alle MyInterface-Abhängigkeiten in MyClass instanziieren würden.

Ein weiterer Grund ist zum Testen.
Wenn Sie eine Abhängigkeit von einer Dateireader-Schnittstelle haben und diese Abhängigkeit über einen Konstruktor beispielsweise in ConsumerClass einfügen, können Sie während des Tests eine speicherinterne Implementierung des Dateireader an ConsumerClass übergeben, ohne dass dies erforderlich ist I / O während des Testens.

Stefan Billiet
quelle
13
Das ist großartig, gut erklärt. Bitte akzeptieren Sie eine imaginäre +1, da mein Mitarbeiter dies noch nicht zulässt!
MyDaftQuestions
11
Das komplizierte Konstruktionsszenario ist ein guter Kandidat für die Verwendung des Factory-Musters. Auf diese Weise übernimmt die Fabrik die Verantwortung für das Konstruieren des Objekts, sodass die Klasse selbst dies nicht muss und Sie sich an das Prinzip der Einzelverantwortung halten.
Matt Gibson
1
@MattGibson guter Punkt.
Stefan Billiet
2
+1 zum Testen, eine gut strukturierte DI beschleunigt sowohl das Testen als auch die Durchführung von Komponententests in der Klasse, die Sie testen.
Umur Kontacı
52

Wie DI implementiert werden kann, hängt stark von der verwendeten Sprache ab.

Hier ist ein einfaches Nicht-DI-Beispiel:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo() {
        bar = new Bar();
        qux = new Qux();
    }
}

Das nervt zB wenn ich für einen Test ein Scheinobjekt verwenden möchte bar. So können wir dies flexibler gestalten und die Übergabe von Instanzen über den Konstruktor ermöglichen:

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo(Bar bar, Qux qux) {
        this.bar = bar;
        this.qux = qux;
    }
}

// in production:
new Foo(new Bar(), new Qux());
// in test:
new Foo(new BarMock(), new Qux());

Dies ist bereits die einfachste Form der Abhängigkeitsinjektion. Das ist aber trotzdem schade, weil alles manuell erledigt werden muss (auch, weil der Anrufer einen Verweis auf unsere internen Objekte halten und damit unseren Zustand ungültig machen kann).

Wir können mehr Abstraktion einführen, indem wir Fabriken verwenden:

  • Eine Möglichkeit besteht darin Foo, dass die von einer abstrakten Factory generiert wird

    interface FooFactory {
        public Foo makeFoo();
    }
    
    class ProductionFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new Bar(), new Baz()) }
    }
    
    class TestFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new BarMock(), new Baz()) }
    }
    
    FooFactory fac = ...; // depends on test or production
    Foo foo = fac.makeFoo();
  • Eine andere Möglichkeit besteht darin, eine Factory an den Konstruktor zu übergeben:

    interface DependencyManager {
        public Bar makeBar();
        public Qux makeQux();
    }
    class ProductionDM implements DependencyManager {
        public Bar makeBar() { return new Bar() }
        public Qux makeQux() { return new Qux() }
    }
    class TestDM implements DependencyManager {
        public Bar makeBar() { return new BarMock() }
        public Qux makeQux() { return new Qux() }
    }
    
    class Foo {
        private Bar bar;
        private Qux qux;
    
        public Foo(DependencyManager dm) {
            bar = dm.makeBar();
            qux = dm.makeQux();
        }
    }

Die verbleibenden Probleme dabei sind, dass wir DependencyManagerfür jede Konfiguration eine neue Unterklasse schreiben müssen und dass die Anzahl der Abhängigkeiten, die verwaltet werden können, ziemlich begrenzt ist (für jede neue Abhängigkeit ist eine neue Methode in der Schnittstelle erforderlich).

Mit Funktionen wie Reflektion und dynamischem Laden von Klassen können wir dies umgehen. Dies hängt jedoch stark von der verwendeten Sprache ab. In Perl können Klassen anhand ihres Namens referenziert werden, was ich auch tun könnte

package Foo {
    use signatures;

    sub new($class, $dm) {
        return bless {
            bar => $dm->{bar}->new,
            qux => $dm->{qux}->new,
        } => $class;
    }
}

my $prod = { bar => 'My::Bar', qux => 'My::Qux' };
my $test = { bar => 'BarMock', qux => 'QuxMock' };
$test->{bar} = 'OtherBarMock';  # change conf at runtime

my $foo = Foo->new(rand > 0.5 ? $prod : $test);

In Sprachen wie Java könnte sich der Abhängigkeitsmanager wie folgt verhalten Map<Class, Object>:

Bar bar = dm.make(Bar.class);

Zu welcher tatsächlichen Klasse das Bar.classaufgelöste Objekt gehört, kann zur Laufzeit konfiguriert werden, z Map<Class, Class>.

Map<Class, Class> dependencies = ...;

public <T> T make(Class<T> c) throws ... {
    // plus a lot more error checking...
    return dependencies.get(c).newInstance();
}

Beim Schreiben des Konstruktors ist noch ein manuelles Element erforderlich. Wir können den Konstruktor aber komplett überflüssig machen, indem wir zB den DI über Annotationen ansteuern:

class Foo {
    @Inject(Bar.class)
    private Bar bar;

    @Inject(Qux.class)
    private Qux qux;

    ...
}

dm.make(Foo.class);  // takes care of initializing "bar" and "qux"

Hier ist eine Beispielimplementierung eines winzigen (und sehr eingeschränkten) DI-Frameworks: http://ideone.com/b2ubuF , obwohl diese Implementierung für unveränderliche Objekte völlig unbrauchbar ist (diese naive Implementierung kann keine Parameter für den Konstruktor annehmen).

amon
quelle
7
Das ist super mit großartigen Beispielen, obwohl ich ein paar Mal lesen muss, um alles zu verdauen, aber danke, dass ich mir so viel Zeit genommen habe.
MyDaftQuestions
33
Es macht Spaß, wie jede nächste Vereinfachung komplexer ist
Kromster
48

Unsere Freunde von Stack Overflow haben eine gute Antwort darauf . Mein Favorit ist die zweite Antwort, einschließlich des Zitats:

"Dependency Injection" ist ein 25-Dollar-Begriff für ein 5-Cent-Konzept. (...) Abhängigkeitsinjektion bedeutet, einem Objekt seine Instanzvariablen zuzuweisen. (...).

aus James Shores Blog . Natürlich gibt es komplexere Versionen / Muster, die darüber geschichtet sind, aber es reicht aus, um im Grunde zu verstehen, was vor sich geht. Anstatt dass ein Objekt eigene Instanzvariablen erstellt, werden diese von außen übergeben.

user967543
quelle
10
Ich hatte das gelesen ... und bis jetzt machte es keinen Sinn ... Jetzt macht es Sinn. Abhängigkeit ist eigentlich kein so großes Konzept (egal wie mächtig es ist), aber es hat einen großen Namen und eine Menge Internet-Lärm!
MyDaftQuestions
18

Nehmen wir an, Sie gestalten das Interieur Ihres Wohnzimmers: Sie bringen einen schicken Kronleuchter an Ihrer Decke an und schließen eine edel passende Stehlampe an. Ein Jahr später beschließt Ihre reizende Frau, dass sie das Zimmer etwas weniger förmlich haben möchte. Welche Leuchte lässt sich leichter wechseln?

Die Idee hinter DI ist, den "Plug-In" -Ansatz überall zu verwenden. Dies scheint keine große Sache zu sein, wenn Sie in einer einfachen Hütte ohne Trockenbau leben und alle elektrischen Drähte freiliegen. (Sie sind ein Handwerker - Sie können alles ändern!) Und in ähnlicher Weise kann DI bei einer kleinen und einfachen Anwendung mehr Komplikationen verursachen, als es wert ist.

Für eine große und komplexe Anwendung ist DI jedoch für eine langfristige Wartung unverzichtbar . Sie können damit ganze Module aus Ihrem Code "entfernen" (z. B. das Datenbank-Backend) und sie mit verschiedenen Modulen austauschen, die das gleiche Ziel erreichen, dies jedoch auf eine völlig andere Weise tun (z. B. ein Cloud-Speichersystem).

Übrigens wäre eine Erörterung von DI unvollständig, ohne die Empfehlung von Dependency Injection in .NET von Mark Seeman. Wenn Sie mit .NET vertraut sind und jemals beabsichtigen, an einer umfangreichen SW-Entwicklung mitzuwirken, ist dies eine wichtige Lektüre. Er erklärt das Konzept viel besser als ich.

Lassen Sie mich Ihnen ein letztes wesentliches Merkmal von DI überlassen, das in Ihrem Codebeispiel übersehen wird. DI ermöglicht es Ihnen, das enorme Potenzial an Flexibilität zu nutzen, das in dem Sprichwort " Program to interfaces " enthalten ist. So ändern Sie Ihr Beispiel geringfügig:

public void Main()
{
    ILightFixture fixture = new ClassyChandelier();
    MyRoom room = new MyRoom (fixture);
}
...
public MyRoom(ILightFixture fixture)
{
    this.MyLightFixture = fixture ; 
}

Aber JETZT kann ich , da MyRoomes für die ILightFixture- Benutzeroberfläche entwickelt wurde , problemlos im nächsten Jahr eine Zeile in der Hauptfunktion ändern (eine andere "Abhängigkeit" "injizieren") und bekomme sofort neue Funktionen in MyRoom (ohne dies tun zu müssen) Erstellen Sie MyRoom neu oder stellen Sie es erneut bereit, falls es sich in einer separaten Bibliothek befindet. Dies setzt natürlich voraus, dass alle Ihre ILightFixture-Implementierungen unter Berücksichtigung der Liskov-Substitution entwickelt wurden. Alles mit nur einer einfachen Änderung:

public void Main()
{
    ILightFixture fixture = new MuchLessFormalLightFixture();
    MyRoom room = new MyRoom (fixture);
}

Außerdem kann ich jetzt Unit Test MyRoom(Sie sind Unit Test, oder?) Und eine Scheinleuchte verwenden, mit der ich MyRoomvöllig unabhängig testen kannClassyChandelier.

Natürlich gibt es noch viele weitere Vorteile, aber diese haben mir die Idee verkauft.

kmote
quelle
Ich wollte mich bedanken, das war hilfreich für mich, aber sie möchten nicht, dass Sie das tun, sondern dass Sie mithilfe von Kommentaren Verbesserungen vorschlagen. Wenn Sie Lust dazu haben, können Sie einen der anderen Vorteile hinzufügen, die Sie erwähnt haben. Aber sonst, danke, war das hilfreich für mich.
Steve
2
Ich interessiere mich auch für das Buch, auf das Sie verwiesen haben, aber ich finde es alarmierend, dass es ein 584-seitiges Buch zu diesem Thema gibt.
Steve
4

Die Abhängigkeitsinjektion übergibt nur einen Parameter, dies führt jedoch immer noch zu einigen Problemen, und der Name soll sich ein wenig darauf konzentrieren, warum der Unterschied zwischen dem Erstellen eines Objekts und dem Übergeben eines Objekts wichtig ist.

Dies ist wichtig, da (ziemlich offensichtlich) jedes Mal, wenn Objekt A Typ B aufruft, A davon abhängt, wie B funktioniert. Das heißt, wenn A die Entscheidung trifft, welchen konkreten Typ von B es verwenden soll, geht viel Flexibilität verloren, wie A von externen Klassen verwendet werden kann, ohne A zu ändern.

Ich erwähne dies, weil Sie davon sprechen, den Punkt der Abhängigkeitsinjektion zu verfehlen. Es kann nur bedeuten, einen Parameter zu übergeben, aber es kann wichtig sein, ob Sie dies tun.

Einige der Schwierigkeiten bei der tatsächlichen Implementierung von DI treten auch bei großen Projekten besser auf. Im Allgemeinen wird die tatsächliche Entscheidung, welche konkreten Klassen verwendet werden sollen, ganz nach oben verschoben, sodass Ihre Startmethode wie folgt aussehen kann:

public void Start()
{
    MySubSubInterfaceA mySubSubInterfaceA = new mySubSubInterfaceA();
    MySubSubInterfaceB mySubSubInterfaceB = new mySubSubInterfaceB();     
    MySubInterface mySubInterface = new MySubInterface(mySubSubInterfaceA,mySubSubInterfaceB);
    MyInterface myInterface = new MyInterface(MySubInterface);
    MyClass class = new MyClass(myInterface);
}

aber mit weiteren 400 Zeilen dieser Art von Nietcode. Wenn Sie sich vorstellen, dies im Laufe der Zeit beizubehalten, wird die Attraktivität von DI-Containern deutlicher.

Stellen Sie sich auch eine Klasse vor, die beispielsweise IDisposable implementiert. Wenn die Klassen, die es verwenden, es per Injection erhalten, anstatt es selbst zu erstellen, woher wissen sie, wann sie Dispose () aufrufen müssen? (Ein weiteres Problem, mit dem sich ein DI-Container befasst.)

Sicher, die Abhängigkeitsinjektion übergibt nur einen Parameter, aber wenn die Leute Abhängigkeitsinjektion sagen, meinen sie "Übergeben eines Parameters an Ihr Objekt im Gegensatz zu der Alternative, dass Ihr Objekt selbst etwas instanziiert", und den Umgang mit allen Vorteilen der Nachteile von ein solcher Ansatz, möglicherweise mit Hilfe eines DI-Containers.

bA
quelle
Das sind viele Subs und Interfaces! Wollen Sie eine Schnittstelle anstelle einer Klasse, die die Schnittstelle implementiert, neu einrichten? Scheint falsch.
JBRWilkinson
@ JBRWilkinson Ich meine eine Klasse, die eine Schnittstelle implementiert. Ich sollte das klarer machen. Der Punkt ist jedoch, dass eine große App Klassen mit Abhängigkeiten hat, die wiederum Abhängigkeiten haben, die wiederum Abhängigkeiten haben ... Das manuelle Einrichten aller Klassen in der Main-Methode ist das Ergebnis vieler Konstruktorinjektionen.
PSR
4

Auf Klassenebene ist es einfach.

"Abhängigkeitsinjektion" beantwortet einfach die Frage "Wie finde ich meine Mitarbeiter?" Mit "Sie werden an Sie herangeführt - Sie müssen sie nicht selbst holen". (Es ist ähnlich wie - aber nicht dasselbe wie - "Inversion of Control", wo die Frage "Wie ordne ich meine Operationen über meine Eingaben an?" Eine ähnliche Antwort hat.)

Der einzige Vorteil, den Ihre Mitarbeiter haben, besteht darin, dass der Client-Code Ihre Klasse verwenden kann, um ein Objektdiagramm zu erstellen, das seinen aktuellen Anforderungen entspricht. Sie haben die Form und die Wandlungsfähigkeit des Diagramms nicht willkürlich durch eine private Entscheidung festgelegt die konkreten Arten und den Lebenszyklus Ihrer Mitarbeiter.

(Alle anderen Vorteile der Testbarkeit, der losen Kopplung usw. ergeben sich größtenteils aus der Verwendung von Schnittstellen und weniger aus der Abhängigkeitsinjektion, obwohl DI natürlich die Verwendung von Schnittstellen fördert).

Es ist erwähnenswert, dass, wenn Sie vermeiden Sie Ihre eigenen Mitarbeiter instanziiert wird , Ihre Klasse muss daher seine Mitarbeiter von einem Konstruktor, eine Eigenschaft erhalten, oder ein Verfahren Argument (diese letztere Option wird oft übersehen, übrigens ... es tut Es ist nicht immer sinnvoll, dass die Kollaborateure einer Klasse Teil ihres "Staates" sind.

Und das ist auch gut so.

Auf der Anwendungsebene ...

Soviel zur klassenbezogenen Sicht der Dinge. Angenommen, Sie haben eine Reihe von Klassen, die der Regel "Ihre eigenen Mitarbeiter nicht instanziieren" folgen, und möchten aus ihnen einen Antrag stellen. Am einfachsten ist es, guten alten Code (ein wirklich nützliches Tool zum Aufrufen von Konstruktoren, Eigenschaften und Methoden!) Zu verwenden, um das gewünschte Objektdiagramm zu erstellen und Eingaben zu tätigen. (Ja, einige dieser Objekte in Ihrem Diagramm werden selbst Objektfabriken sein, die als Kollaborateure an andere langlebige Objekte im Diagramm weitergegeben wurden, die für den Service bereit sind ... Sie können nicht jedes Objekt vorkonstruieren! ).

... Sie müssen den Objektgraphen Ihrer App "flexibel" konfigurieren ...

Abhängig von Ihren anderen (nicht codebezogenen) Zielen möchten Sie dem Endbenutzer möglicherweise eine gewisse Kontrolle über das so bereitgestellte Objektdiagramm geben. Dies führt Sie in die eine oder andere Richtung eines Konfigurationsschemas, sei es eine Textdatei Ihres eigenen Designs mit einigen Name / Wert-Paaren, eine XML-Datei, eine benutzerdefinierte DSL, eine deklarative Graph-Beschreibungssprache wie YAML, eine zwingende Skriptsprache wie JavaScript oder etwas anderes, das für die jeweilige Aufgabe geeignet ist. Was auch immer erforderlich ist, um ein gültiges Objektdiagramm so zu erstellen, dass es den Anforderungen Ihrer Benutzer entspricht.

... kann eine bedeutende Designkraft sein.

Unter den extremsten Umständen dieses Typs können Sie sich für einen sehr allgemeinen Ansatz entscheiden und den Endbenutzern einen allgemeinen Mechanismus zum "Verkabeln" des von ihnen gewählten Objektgraphen an die Hand geben und ihnen sogar die Möglichkeit geben, konkrete Realisierungen von Schnittstellen zu erstellen Laufzeit! (Ihre Dokumentation ist ein glänzendes Juwel, Ihre Benutzer sind sehr schlau, kennen zumindest den groben Umriss des Objektgraphen Ihrer Anwendung, haben aber zufällig keinen Compiler zur Hand). Dieses Szenario kann theoretisch in einigen Unternehmenssituationen auftreten.

In diesem Fall verfügen Sie wahrscheinlich über eine deklarative Sprache, mit der Ihre Benutzer jeden Typ, die Zusammensetzung eines Objektgraphen dieser Typen und eine Palette von Schnittstellen, die der mythische Endbenutzer mischen und zuordnen kann, ausdrücken können. Um die kognitive Belastung Ihrer Benutzer zu verringern, bevorzugen Sie einen "Configuration by Convention" -Ansatz, sodass sie nur das interessierende Objekt-Graph-Fragment eingeben und überschreiben müssen, anstatt mit dem Ganzen zu ringen.

Du armer Arsch!

Da Sie nicht alles selbst aufschreiben wollten (aber im Ernst, sehen Sie sich eine YAML-Bindung für Ihre Sprache an), verwenden Sie eine Art DI-Framework.

Abhängig von der Reife dieses Frameworks haben Sie möglicherweise nicht die Möglichkeit, Konstruktor-Injection zu verwenden, auch wenn dies sinnvoll ist (die Mitarbeiter ändern sich nicht über die Lebensdauer eines Objekts), sodass Sie gezwungen sind, Setter Injection zu verwenden (auch wenn Kollaborateure ändern sich nicht über die Lebensdauer eines Objekts, und selbst wenn es keinen logischen Grund gibt, warum alle konkreten Implementierungen einer Schnittstelle Kollaborateure eines bestimmten Typs haben müssen . Wenn ja, befinden Sie sich derzeit in der Hölle der starken Kopplung, obwohl Sie in Ihrer gesamten Codebasis fleißig Schnittstellen verwendet haben - Horror!

Hoffentlich haben Sie ein DI-Framework verwendet, mit dem Sie Konstruktorinjektionen durchführen können, und Ihre Benutzer sind nur ein bisschen mürrisch , weil sie nicht mehr Zeit damit verbringen, über die spezifischen Dinge nachzudenken, die sie zum Konfigurieren benötigen, und ihnen eine für die Aufgabe besser geeignete Benutzeroberfläche zu geben zur Hand. (Um fair zu sein, haben Sie wahrscheinlich versucht, einen Weg zu finden, aber JavaEE hat Sie im Stich gelassen und Sie mussten auf diesen schrecklichen Hack zurückgreifen).

Bootnote

Zu keinem Zeitpunkt verwenden Sie Google Guice, wodurch der Codierer die Aufgabe, einen Objektgraphen mit Code zu erstellen, überflüssig macht ... indem er Code schreibt. Argh!

David Bullock
quelle
0

Viele Leute sagten hier, DI sei ein Mechanismus, um die Initialisierung von Instanzvariablen auszulagern.

Für offensichtliche und einfache Beispiele benötigen Sie keine "Abhängigkeitsinjektion". Sie können dies tun, indem Sie einen einfachen Parameter an den Konstruktor übergeben, wie Sie in Frage gestellt haben.

Ich denke jedoch, dass ein dedizierter "Abhängigkeitsinjektions" -Mechanismus nützlich ist, wenn Sie über Ressourcen auf Anwendungsebene sprechen, bei denen sowohl der Anrufer als auch der Angerufene nichts über diese Ressourcen wissen (und es nicht wissen wollen!).

Zum Beispiel: Datenbankverbindung, Sicherheitskontext, Protokollierungsfunktion, Konfigurationsdatei usw.

Darüber hinaus ist im Allgemeinen jeder Singleton ein guter Kandidat für DI. Je mehr davon Sie haben und verwenden, desto mehr Chancen haben Sie, DI zu benötigen.

Für diese Art von Ressourcen ist es ziemlich klar, dass es keinen Sinn macht, sie über Parameterlisten zu verschieben, und daher wurde der DI erfunden.

Letzter Hinweis: Sie benötigen auch einen DI-Container, der die eigentliche Injektion ausführt ... (erneut, um sowohl den Aufrufer als auch den Angerufenen von den Implementierungsdetails zu befreien).

Gamliela
quelle
-2

Wenn Sie einen abstrakten Referenztyp übergeben, kann der aufrufende Code jedes Objekt übergeben, das den abstrakten Typ erweitert / implementiert. Daher könnte die Klasse, die das Objekt verwendet, in Zukunft ein neues Objekt verwenden, das erstellt wurde, ohne den Code zu ändern.

DavidB
quelle
-4

Dies ist eine weitere Option neben der Antwort von amon

Builder verwenden:

Klasse Foo {
    private Bar Bar;
    private Qux qux;

    private Foo () {
    }

    public Foo (Builder builder) {
        this.bar = builder.bar;
        this.qux = builder.qux;
    }

    öffentliche statische Klasse Builder {
        private Bar Bar;
        private Qux qux;
        public Buider setBar (Bar bar) {
            this.bar = bar;
            gib das zurück;
        }
        public Builder setQux (Qux qux) {
            this.qux = qux;
            gib das zurück;
        }
        public Foo build () {
            gib neues Foo zurück (dies);
        }
    }
}

// in Produktion:
Foo foo = neuer Foo.Builder ()
    .setBar (neue Leiste ())
    .setQux (neues Qux ())
    .bauen();

// im Test:
Foo testFoo = neuer Foo.Builder ()
    .setBar (neues BarMock ())
    .setQux (neues Qux ())
    .bauen();

Das benutze ich für Abhängigkeiten. Ich benutze kein DI-Framework. Sie stehen im Weg.

user3155341
quelle
4
Dies trägt nicht viel zu den anderen Antworten von vor einem Jahr bei und bietet keine Erklärung dafür, warum ein Builder dem Fragesteller helfen könnte, die Abhängigkeitsinjektion zu verstehen.
@ Schneemann Es ist eine andere Option anstelle von DI. Es tut mir leid, dass Sie das nicht so sehen. Vielen Dank für die Abstimmung.
user3155341
1
Der Fragesteller wollte verstehen, ob DI wirklich so einfach ist wie die Übergabe eines Parameters. Er fragte nicht nach Alternativen zu DI.
1
Die Beispiele des OP sind sehr gut. Ihre Beispiele bieten mehr Komplexität für etwas, das eine einfache Idee ist. Sie veranschaulichen nicht das Problem der Frage.
Adam Zuckerman
Konstruktor DI übergibt einen Parameter. Es sei denn, Sie übergeben ein Schnittstellenobjekt anstelle eines konkreten Klassenobjekts. Es ist diese Abhängigkeit von einer konkreten Implementierung, die über DI gelöst wird. Der Konstruktor weiß nicht, welcher Typ übergeben wird, und es interessiert ihn auch nicht. Er weiß nur, dass er einen Typ empfängt, der eine Schnittstelle implementiert. Ich schlage vor, PluralSight, Es hat einen großen Anfänger DI / IOC-Kurs.
Hardgraf