web-dev-qa-db-fra.com

Qu'est-ce que l'injection de constructeur?

J'ai examiné les termes injection de constructeur et injection de dépendance tout en parcourant des articles sur les modèles de conception (localisateur de services).

Lorsque j'ai cherché sur l'injection de constructeur sur Google, j'ai obtenu des résultats peu clairs, ce qui m'a incité à m'enregistrer ici.

Qu'est-ce que l'injection de constructeur? S'agit-il d'un type spécifique d'injection de dépendance? Un exemple canonique serait d'une grande aide!

Modifier

Revisitant ces questions après une interruption d'une semaine, je peux voir à quel point j'étais perdu ... Juste au cas où quelqu'un d'autre viendrait ici, je mettrai à jour le corps de la question avec un peu d'apprentissage. N'hésitez pas à commenter/corriger. L'injection de constructeur et l'injection de propriété sont deux types d'injection de dépendance.

51
TheSilverBullet

Je ne suis pas un expert, mais je pense que je peux vous aider. Et oui, c'est un type spécifique d'injection de dépendance.

Avertissement: Presque tout cela a été "volé" à le Wiki Ninject

Examinons l'idée de l'injection de dépendance en parcourant un exemple simple. Disons que vous écrivez le prochain jeu à succès, où les nobles guerriers se battent pour une grande gloire. Tout d'abord, nous aurons besoin d'une arme appropriée pour armer nos guerriers.

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

Ensuite, créons une classe pour représenter nos guerriers eux-mêmes. Pour attaquer ses ennemis, le guerrier aura besoin d'une méthode Attack (). Lorsque cette méthode est appelée, elle doit utiliser son épée pour frapper son adversaire.

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

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

Maintenant, nous pouvons créer nos samouraïs et nous battre!

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

Comme vous pouvez l'imaginer, cela affichera Coupez les malfaiteurs en deux sur la console. Cela fonctionne très bien, mais que se passerait-il si nous voulions armer notre samouraï d'une autre arme? Puisque l'épée est créée à l'intérieur du constructeur de la classe Samurai, nous devons modifier l'implémentation de la classe afin d'effectuer ce changement.

Lorsqu'une classe dépend d'une dépendance concrète, on dit qu'elle est étroitement couplée à cette classe . Dans cet exemple, la classe Samurai est étroitement couplée à la classe Sword. Lorsque les classes sont étroitement couplées, elles ne peuvent pas être échangées sans altérer leur implémentation. Afin d'éviter un couplage étroit des classes, nous pouvons utiliser des interfaces pour fournir un niveau d'indirection. Créons une interface pour représenter une arme dans notre jeu.

interface IWeapon
{
    void Hit(string target);
}

Ensuite, notre classe Sword peut implémenter cette interface:

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

Et nous pouvons modifier notre classe de samouraï:

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

Maintenant, nos samouraïs peuvent être armés de différentes armes. Mais attendez! L'épée est toujours créée à l'intérieur du constructeur de Samurai. Comme nous devons encore modifier la mise en œuvre de Samurai afin de donner à notre guerrier une autre arme, Samurai est toujours étroitement lié à Sword.

Heureusement, il existe une solution simple. Plutôt que de créer l'épée à partir du constructeur de Samurai, nous pouvons l'exposer en tant que paramètre du constructeur à la place. Également connu sous le nom d'injection de constructeur.

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

Comme l'a souligné Giorgio, il y a aussi l'injection de propriété. Ce serait quelque chose comme:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


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

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

}

J'espère que cela t'aides.

99
Luiz Angelo

Je vais essayer autant que possible de rendre cela très élémentaire. De cette façon, vous pouvez vous appuyer sur le concept et créer vos propres solutions ou idées complexes.

Imaginez maintenant que nous avons une église appelée Jubilee, qui a des branches dans le monde entier. Notre objectif est d'obtenir simplement un article dans chaque branche. Voici la solution avec DI;

1) Créez une interface IJubilee:

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

2) Créez une classe JubileeDI qui prendrait l'interface IJubilee comme constructeur et retournerait un élément:

public class JubileeDI
{
    readonly IJubilee jubilee;

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

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

3) Créez maintenant trois braches de Jubilee à savoir JubileeGT, JubileeHOG, JubileeCOV, qui DOIVENT tous hériter de l'interface IJubilee. Pour le plaisir, faites en sorte que l'un d'eux implémente sa méthode comme virtuelle:

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) C'est tout! Voyons maintenant DI en action:

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

Pour chaque instance de la classe conteneur, nous transmettons une nouvelle instance de la classe dont nous avons besoin, ce qui permet un couplage lâche.

J'espère que cela explique le concept en un mot.

3
David Grant

Supposons que vous ayez une classe A dont chaque instance a besoin d'une instance d'une autre classe B.

class A
{
   B b;
}

L'injection de dépendances signifie que la référence à B est définie par l'objet qui gère l'instance de A (par opposition à la classe A gérant la référence à B directement).

L'injection de constructeur signifie que la référence à B est passée en paramètre au constructeur de A et définie dans le constructeur:

class A
{
    B b;

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

Une alternative consiste à utiliser une méthode setter (ou une propriété) pour définir la référence sur B. Dans ce cas, l'objet qui gère une instance a de A doit d'abord appeler le constructeur de A puis appeler la méthode setter pour définir la variable membre A.b avant a est utilisé. Cette dernière méthode d'injection est nécessaire lorsque les objets contiennent des références cycliques les uns aux autres, par exemple si une instance b de B définie dans une instance a de A contient une référence arrière à a.

** ÉDITER**

Voici quelques détails supplémentaires pour répondre au commentaire.

1. La classe A gère l'instance B

class A
{
    B b;

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

2. L'instance B est définie dans le constructeur

class A
{
    B b;

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

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

. L'instance B est définie à l'aide de la méthode de définition

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