Was ist Konstruktorinjektion?

47

Ich habe mir die Begriffe Konstruktorinjektion und Abhängigkeitsinjektion angesehen, während ich Artikel über Entwurfsmuster (Service Locator) durchgesehen habe.

Als ich über die Konstruktorinjektion googelte, bekam ich unklare Ergebnisse, die mich dazu veranlassten, hier einzuchecken.

Was ist Konstruktorinjektion? Handelt es sich um eine bestimmte Art der Abhängigkeitsinjektion? Ein kanonisches Beispiel wäre eine große Hilfe!

Bearbeiten

Wenn ich diese Fragen nach einer Pause von einer Woche noch einmal besuche, kann ich sehen, wie verloren ich war ... Nur für den Fall, dass jemand anderes hier vorbeischaut, werde ich den Fragenkörper mit ein wenig Wissen von mir aktualisieren. Bitte zögern Sie nicht zu kommentieren / zu korrigieren. Konstruktor-Injection und Property-Injection sind zwei Arten von Dependency-Injection.

TheSilverBullet
quelle
5
Erster Schlag in Google erklärt es klar ... misko.hevery.com/2009/02/19/…
user281377

Antworten:

95

Ich bin kein Experte, aber ich glaube, ich kann helfen. Und ja, es ist eine bestimmte Art von Abhängigkeitsinjektion.

Haftungsausschluss: Fast alles wurde aus dem Ninject Wiki "gestohlen"

Lassen Sie uns die Idee der Abhängigkeitsinjektion anhand eines einfachen Beispiels untersuchen. Angenommen, Sie schreiben das nächste Blockbuster-Spiel, in dem edle Krieger um großen Ruhm kämpfen. Zuerst brauchen wir eine Waffe, mit der wir unsere Krieger bewaffnen können.

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Dann wollen wir eine Klasse schaffen, die unsere Krieger selbst repräsentiert. Um seine Feinde anzugreifen, benötigt der Krieger eine Attack () -Methode. Wenn diese Methode aufgerufen wird, sollte es sein Schwert verwenden, um seinen Gegner zu schlagen.

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

Jetzt können wir unseren Samurai erschaffen und kämpfen!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

Wie Sie sich vorstellen können, druckt dies Chopped the Evildoers sauber zur Hälfte auf die Konsole. Das funktioniert gut, aber was ist, wenn wir unseren Samurai mit einer anderen Waffe ausrüsten wollen? Da das Schwert im Konstruktor der Samurai-Klasse erstellt wird, müssen wir die Implementierung der Klasse ändern, um diese Änderung vorzunehmen.

Wenn eine Klasse von einer konkreten Abhängigkeit abhängig ist, heißt es engen Kopplung mit dieser Klasse . In diesem Beispiel ist die Samurai-Klasse eng mit der Schwert-Klasse verbunden. Wenn Klassen eng miteinander verbunden sind, können sie nicht ausgetauscht werden, ohne ihre Implementierung zu ändern. Um eine enge Kopplung der Klassen zu vermeiden, können wir Interfaces verwenden, um eine Indirektionsebene bereitzustellen. Lassen Sie uns eine Benutzeroberfläche erstellen, die eine Waffe in unserem Spiel darstellt.

interface IWeapon
{
    void Hit(string target);
}

Dann kann unsere Sword-Klasse diese Schnittstelle implementieren:

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Und wir können unsere Samurai-Klasse ändern:

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Jetzt können unsere Samurai mit verschiedenen Waffen bewaffnet werden. Aber warte! Das Schwert wird immer noch im Samurai-Konstruktor hergestellt. Da wir die Implementierung von Samurai noch ändern müssen, um unserem Krieger eine weitere Waffe zu geben, ist Samurai immer noch eng mit Sword verbunden.

Zum Glück gibt es eine einfache Lösung. Anstatt das Schwert im Konstruktor von Samurai zu erstellen, können wir es stattdessen als Parameter des Konstruktors anzeigen. Wird auch als Konstruktorinjektion bezeichnet.

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Wie Giorgio betonte, gibt es auch Immobilieninjektionen. Das wäre so etwas wie:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

Hoffe das hilft.

Luiz Angelo
quelle
8
Liebte dieses! :) Es liest sich tatsächlich wie eine Geschichte! Nur neugierig. Wenn Sie denselben Samurai (nacheinander) mit mehreren Waffen ausrüsten möchten, ist die Injektion von Eigentum der beste Weg, oder?
TheSilverBullet
Ja, das könntest du machen. Eigentlich denke ich, dass es besser ist, die IWeapon als Parameter an die Attack-Methode zu übergeben. Wie "Public Void Attack (IWeapon with, string target)". Und um doppelten Code zu vermeiden, würde ich die vorhandene Methode "public void Attack (string target)" intakt machen und "this.Attack (this.weapon, target)" aufrufen.
Luiz Angelo
In Kombination mit Fabriken erhalten Sie Methoden wie CreateSwordSamurai ()! Nettes Beispiel.
Geerten
2
Tolles Beispiel! Also klar ...
Serge van den Oever
@Luiz Angelo. Tut mir leid, ich bin mir nicht sicher, ob ich Ihren Kommentar verstanden habe: "Eigentlich wäre es besser, wenn Sie die IWeapon als Parameter an die Attack-Methode übergeben würden. Wie ...". Sie meinen, die Attack-Methode in der Samurai-Klasse zu überlasten?
Pap
3

Ich werde so viel wie möglich versuchen, um dies sehr elementar zu machen. Auf diese Weise können Sie auf dem Konzept aufbauen und Ihre eigenen komplexen Lösungen oder Ideen erstellen.

Stellen Sie sich nun vor, wir hätten eine Kirche namens Jubiläum, die weltweit Zweigstellen hat. Unser Ziel ist es, einfach einen Artikel aus jeder Filiale zu bekommen. Hier wäre die Lösung mit DI;

1) Erstellen Sie eine Schnittstelle IJubilee:

public interface IJubilee
{
    string GetItem(string userInput);
}

2) Erstellen Sie eine Klasse JubileeDI, die die IJubilee-Schnittstelle als Konstruktor verwendet und ein Element zurückgibt:

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3) Erstellen Sie nun drei Jubiläumsbrachen, JubileeGT, JubileeHOG, JubileeCOV, die alle die IJubilee-Schnittstelle erben MÜSSEN. Lassen Sie zum Spaß eine von ihnen die Methode als virtuell implementieren:

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4) Das ist es! Lassen Sie uns nun DI in Aktion sehen:

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

Für jede Instanz der Containerklasse übergeben wir eine neue Instanz der von uns benötigten Klasse, die eine lose Kopplung ermöglicht.

Hoffe, das erklärt das Konzept auf den Punkt.

David Grant
quelle
2

Angenommen, Sie haben eine Klasse, von der Ajede Instanz eine Instanz einer anderen Klasse benötigt B.

class A
{
   B b;
}

Abhängigkeitsinjektion bedeutet, dass die Referenz auf Bvon dem Objekt festgelegt wird, von dem die Instanz verwaltet wird A(im Gegensatz dazu, dass die Klasse Adie Referenz Bdirekt verwaltet).

Konstruktorinjektion bedeutet, dass die Referenz auf Bals Parameter an den Konstruktor von übergeben Aund im Konstruktor gesetzt wird:

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

Alternativ können Sie eine Setter-Methode (oder eine Eigenschaft) verwenden, um den Verweis auf festzulegen B. In diesem Fall muss das Objekt, das eine Instanz avon verwaltet, Azuerst den Konstruktor von Aaufrufen und später die Setter-Methode aufrufen, um die Member-Variable festzulegen , A.bbevor aes verwendet wird. Die letztere Injektionsmethode ist erforderlich, wenn Objekte zyklische Verweise aufeinander enthalten, z. B. wenn eine Instanz bdavon Bin einer Instanz avon Aeinen Rückverweis auf enthälta .

** EDIT **

Hier sind einige Details, um den Kommentar zu adressieren.

1. Klasse A verwaltet die B-Instanz

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. B-Instanz wird im Konstruktor gesetzt

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. Die B-Instanz wird mit der Setter-Methode festgelegt

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}
Giorgio
quelle
... im Gegensatz dazu, dass die Klasse A den Verweis auf B direkt verwaltet . Was bedeutet das? Wie würden Sie es in Code tun? (Entschuldigen Sie meine Unwissenheit.)
TheSilverBullet
1
Das Samurai-Beispiel ist so viel besser. Lassen Sie uns alle bitte aufhören, Buchstaben für Variablen zu verwenden ...
stuartdotnet
@ stuartdotnet: Ich bestreite nicht, dass das Samurai-Beispiel besser oder schlechter ist, aber warum sollten Sie keine Ein-Buchstaben-Variablen mehr verwenden? Wenn Variablen keine besondere Bedeutung haben, sondern nur Platzhalter sind, sind Variablen mit einem Buchstaben viel kompakter und auf den Punkt gebracht. Längere Namen wie itemAoder objectAnur um den Namen länger zu machen, ist nicht immer besser.
Giorgio
1
Dann haben Sie meinen Punkt verpasst - die Verwendung von nicht beschreibenden Namen ist schwer zu lesen, zu befolgen und zu verstehen und ist keine gute Möglichkeit, einen Punkt zu erklären
stuartdotnet
@stuartdotnet: Ich glaube nicht, dass ich Ihren Standpunkt verfehlt habe: Sie behaupten, dass beschreibende Namen immer besser sind und den Code immer lesbarer und selbsterklärender machen.
Giorgio