Che cos'è l'iniezione del costruttore?


47

Ho esaminato i termini iniezione costruttore e iniezione dipendenza mentre stavo esaminando gli articoli sui modelli di progettazione (Service Locator).

Quando ho cercato su Google l'iniezione del costruttore, ho ottenuto risultati poco chiari, che mi hanno spinto a fare il check-in qui.

Che cos'è l'iniezione del costruttore? È questo un tipo specifico di iniezione di dipendenza? Un esempio canonico sarebbe di grande aiuto!

modificare

Rivisitando queste domande dopo un intervallo di una settimana, posso vedere quanto mi fossi perso ... Nel caso in cui qualcuno si presenti qui, aggiornerò il corpo della domanda con un mio piccolo apprendimento. Non esitate a commentare / correggere. L'iniezione del costruttore e l'iniezione della proprietà sono due tipi di iniezione di dipendenza.


5
Il primo hit in google lo spiega chiaramente ... misko.hevery.com/2009/02/19/…
user281377

Risposte:


95

Non sono un esperto, ma penso di poter aiutare. E sì, è un tipo specifico di iniezione di dipendenza.

Disclaimer: quasi tutto questo è stato "rubato" dal Wiki Ninject

Esaminiamo l'idea dell'iniezione di dipendenza attraverso un semplice esempio. Diciamo che stai scrivendo il prossimo gioco di successo, in cui i nobili guerrieri combattono per la grande gloria. Innanzitutto, avremo bisogno di un'arma adatta per armare i nostri guerrieri.

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

Quindi, creiamo una classe per rappresentare i nostri guerrieri stessi. Per attaccare i suoi nemici, il guerriero avrà bisogno di un metodo Attack (). Quando viene chiamato questo metodo, dovrebbe usare la sua Spada per colpire il suo avversario.

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

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

Ora possiamo creare il nostro samurai e combattere!

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

Come puoi immaginare, questo stampa i malfattori tritati a metà sulla console. Funziona bene, ma se volessimo armare il nostro Samurai con un'altra arma? Poiché la spada viene creata all'interno del costruttore della classe Samurai, dobbiamo apportare modifiche all'implementazione della classe per poter apportare questa modifica.

Quando una classe dipende da una dipendenza concreta, si dice che sia strettamente accoppiata a quella classe . In questo esempio, la classe Samurai è strettamente accoppiata alla classe Sword. Quando le classi sono strettamente accoppiate, non possono essere scambiate senza alterarne l'implementazione. Al fine di evitare le classi strettamente accoppiate, possiamo usare le interfacce per fornire un livello di riferimento indiretto. Creiamo un'interfaccia per rappresentare un'arma nel nostro gioco.

interface IWeapon
{
    void Hit(string target);
}

Quindi, la nostra classe Sword può implementare questa interfaccia:

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

E possiamo modificare la nostra classe Samurai:

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

Ora il nostro Samurai può essere armato con diverse armi. Ma aspetta! La spada è ancora creata all'interno del costruttore di Samurai. Dato che dobbiamo ancora modificare l'implementazione del Samurai per dare al nostro guerriero un'altra arma, il Samurai è ancora strettamente accoppiato alla Spada.

Fortunatamente, esiste una soluzione semplice. Invece di creare la Spada dall'interno del costruttore di Samurai, possiamo invece esporla come parametro del costruttore. Conosciuto anche come Iniezione Costruttore.

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

Come ha sottolineato Giorgio, c'è anche un'iniezione di proprietà. Sarebbe qualcosa del tipo:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


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

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

}

Spero che sia di aiuto.


8
Questo è adorabile! :) In realtà sembra una storia! Solo curioso. Se volessi armare lo stesso Samurai con più armi (in sequenza), l'iniezione di proprietà sarebbe il modo migliore, giusto?
TheSilverBullet

Sì, potresti farlo. In realtà, penso che ti serviresti meglio passando l'IWeapon come parametro al metodo Attack. Come "Public void Attack (IWeapon with, string target)". E per evitare il codice duplicato renderei intatto il metodo esistente "Public void Attack (string target)" e lo chiamerei "this.Attack (this.weapon, target)".
Luiz Angelo,

E poi combinato con le fabbriche, puoi ottenere metodi come CreateSwordSamurai ()! Bell'esempio
Geerten,

2
Ottimo esempio! Così chiaro ...
Serge van den Oever,

@Luiz Angelo. Spiacente, non sono sicuro di aver capito il tuo commento: "In realtà, penso che ti sarebbe meglio servire passando l'IWeapon come parametro al metodo Attack. Come ...". Intendi sovraccaricare il metodo Attack nella classe Samurai?
Pap

3

Cercherò il più possibile di renderlo molto elementare. In questo modo, puoi basarti sul concetto e creare le tue soluzioni o idee complesse.

Ora immagina di avere una chiesa chiamata Jubilee, che ha rami in tutto il mondo. Il nostro obiettivo è semplicemente ottenere un articolo da ogni filiale. Ecco la soluzione con DI;

1) Creare un'interfaccia IJubilee:

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

2) Crea una classe JubileeDI che prenderebbe l'interfaccia IJubilee come costruttore e restituire un oggetto:

public class JubileeDI
{
    readonly IJubilee jubilee;

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

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

3) Ora crea tre braches di Jubilee, ovvero JubileeGT, JubileeHOG, JubileeCOV, che DEVONO tutti ereditare l'interfaccia di IJubilee. Per divertirti, fai in modo che uno di essi implementi il ​​suo metodo come virtuale:

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) Questo è tutto! Ora vediamo DI in azione:

        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!");

Per ogni istanza della classe container, passiamo una nuova istanza della classe richiesta, che consente l'accoppiamento libero.

Spero che questo spieghi il concetto in breve.


2

Supponiamo di avere una classe per Aciascuna istanza di cui è necessaria un'istanza di un'altra classe B.

class A
{
   B b;
}

Iniezione di dipendenza significa che il riferimento a Bè impostato dall'oggetto che gestisce l'istanza di A(anziché avere la classe a Agestire Bdirettamente il riferimento ).

Iniezione del costruttore significa che il riferimento a Bviene passato come parametro al costruttore Ae impostato nel costruttore:

class A
{
    B b;

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

Un'alternativa è utilizzare un metodo setter (o una proprietà) per impostare il riferimento B. In questo caso, l'oggetto che gestisce un'istanza adi Adeve prima chiamare il costruttore Ae successivamente chiamare il metodo setter per impostare la variabile membro A.bprima che avenga utilizzato. Quest'ultimo metodo di iniezione è necessario quando gli oggetti contengono riferimenti ciclici tra loro, ad esempio se un'istanza bdi Bciò è impostata in un'istanza adi Acontiene un riferimento a a.

** MODIFICARE**

Ecco alcuni dettagli in più per rispondere al commento.

1. La classe A gestisce l'istanza B.

class A
{
    B b;

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

2. L'istanza B è impostata nel costruttore

class A
{
    B b;

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

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

3. L'istanza B viene impostata usando il metodo setter

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);
}

... invece di avere la classe A gestire il riferimento B direttamente . Cosa significa questo? Come lo faresti nel codice? (Perdonate la mia ignoranza.)
TheSilverBullet il

1
L'esempio del samurai è molto meglio. Smettiamola tutti di usare lettere per variabili, per favore ...
stuartdotnet,

@stuartdotnet: non ho dubbi sul fatto che l'esempio del samurai sia peggio, ma perché smettere di usare variabili di una lettera? Se le variabili non hanno un significato particolare ma sono solo segnaposto, le variabili a lettera singola sono molto più compatte e mirate. Usare nomi più lunghi come itemAo objectAsolo per allungare il nome non è sempre migliore.
Giorgio,

1
Poi hai perso il mio punto - usare nomi non descrittivi è difficile da leggere, seguire e capire, e non è un buon modo per spiegare un punto
stuartdotnet

@stuartdotnet: Non credo di aver perso il tuo punto: affermi che i nomi descrittivi sono sempre migliori e rendono il codice sempre più leggibile e autoesplicativo.
Giorgio,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.