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

47

Je me suis intéressé aux termes d'injection de constructeur et d'injection de dépendance en passant en revue des articles sur les modèles de conception (service locator).

Quand j'ai cherché sur Google à propos de l'injection de constructeur, j'ai eu des résultats incertains, ce qui m'a incité à vérifier ici.

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

Modifier

En revenant sur ces questions après un intervalle d'une semaine, je peux voir à quel point j'étais perdue ... Juste au cas où quelqu'un d'autre se présenterait ici, je mettrai à jour le corps de la question avec un peu d'apprentissage, le mien. S'il vous plaît n'hésitez pas à commenter / corriger. L'injection de constructeur et l'injection de propriété sont deux types d'injection de dépendance.

La balle d'argent
la source
5
Le premier hit sur Google l'explique clairement ... misko.hevery.com/2009/02/19/…
user281377

Réponses:

95

Je ne suis pas un expert, mais je pense pouvoir aider. Et oui, il s’agit d’un type spécifique d’injection de dépendance.

Disclaimer: Presque tout cela a été "volé" du Wiki Ninject

Examinons l'idée d'injection de dépendance en passant à travers un exemple simple. Disons que vous écrivez le prochain jeu à succès, où de nobles guerriers se battent pour la plus grande gloire. Premièrement, 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. Afin d'attaquer ses ennemis, le guerrier aura besoin d'une méthode Attack (). Lorsque cette méthode est appelée, il 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 combattre!

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

Comme vous pouvez l'imaginer, cela imprimera Chopped les malfaiteurs propres en deux sur la console. Cela fonctionne très bien, mais si nous voulions armer notre Samouraï avec une autre arme? Puisque l’épée est créée dans le constructeur de la classe Samurai, nous devons modifier l’implémentation de la classe pour pouvoir effectuer ce changement.

Lorsqu'une classe dépend d'une dépendance concrète, on dit qu'elle est étroitement liée à cette classe . Dans cet exemple, la classe Samurai est étroitement associée à la classe Sword. Lorsque les classes sont étroitement associées, elles ne peuvent pas être échangées sans modifier 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 une autre arme à notre guerrier, Samurai est toujours étroitement lié à Sword.

Heureusement, il existe une solution facile. 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. É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 existe également une 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.

Luiz Angelo
la source
8
Aimé celui-ci! :) Il se lit réellement comme une histoire! Juste curieux. Si vous vouliez armer le même Samouraï avec plusieurs armes (séquentiellement), l'injection de propriété serait la meilleure solution, n'est-ce pas?
TheSilverBullet
Oui, vous pourriez faire ça. En fait, je pense que vous seriez mieux servi en passant l'IWeapon en tant que paramètre de la méthode Attack. Comme "public void attack (IWeapon avec, string target)". Et pour éviter les doublons de code, je conserverais la méthode existante "Attaque publique publique (cible de chaîne)" et l'appellerais "this.Attack (this.weapon, target)".
Luiz Angelo
Et puis combiné avec des usines, vous pouvez obtenir des méthodes telles que CreateSwordSamurai ()! Bel exemple.
Geerten
2
Excellent exemple! Si clair ...
Serge van den Oever
@ Luiz Angelo. Désolé, je ne suis pas sûr d'avoir compris votre commentaire: "En fait, je pense que vous seriez mieux servi en passant l'IWeapon en tant que paramètre de la méthode Attack. Comme ...". Vous voulez dire surcharger la méthode Attack dans la classe Samurai?
Pap
3

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

Maintenant, imaginez que nous ayons une église appelée Jubilee, qui a des branches dans le monde entier. Notre objectif est de simplement obtenir un article de chaque branche. Voici la solution avec DI;

1) Créer une interface IJubilee:

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

2) Créez une classe JubileeDI qui prendrait l’interface IJubilee en tant que constructeur et renverrait 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 branches de Jubile, à savoir JubileeGT, JubileeHOG et JubileeCOV, qui DOIVENT toutes hériter de l'interface IJubilee. Pour le plaisir, demandez à l’un d’eux d’implémenter sa méthode de manière 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 ça! 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 passons 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 quelques mots.

David Grant
la source
2

Supposons que vous ayez une classe dont Achaque instance nécessite une instance d'une autre classe B.

class A
{
   B b;
}

L'injection de dépendance signifie que la référence à Best définie par l'objet qui gère l'instance de A(au lieu que la classe Agère Bdirectement la référence ).

L'injection de constructeur signifie que la référence à Best passée en tant que paramètre au constructeur de Aet définie dans le constructeur:

class A
{
    B b;

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

Une alternative consiste à utiliser une méthode de définition (ou une propriété) pour définir la référence B. Dans ce cas, l'objet qui gère une instance ade Adoit d'abord appeler le constructeur de A, puis appeler la méthode setter pour définir la variable membre A.bavant qu'elle ne asoit utilisée. Cette dernière méthode d'injection est nécessaire lorsque les objets contiennent des références cycliques, par exemple si une instance bde Bcelle-ci est définie dans une instance ade Acontient une référence arrière à a.

** MODIFIER**

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

3. 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);
}
Giorgio
la source
... par opposition à la classe A qui gère la référence à B directement . Qu'est-ce que ça veut dire? Comment le feriez-vous dans le code? (Pardon mon ignorance.)
TheSilverBullet
1
L'exemple de samouraï est tellement meilleur. Arrêtons tous d'utiliser des lettres pour les variables s'il vous plaît ...
stuartdotnet
@stuartdotnet: Je ne conteste pas que l'exemple du samouraï soit meilleur ou pire, mais pourquoi cesser d'utiliser des variables à une lettre? Si les variables n'ont pas de signification particulière mais ne sont que des espaces réservés, les variables d'une lettre sont beaucoup plus compactes et pertinentes. En utilisant des noms plus longs comme itemAou objectAjuste pour le nom est plus pas toujours mieux.
Giorgio
1
Ensuite, vous avez mal compris. Il est difficile de lire, de suivre et de comprendre des noms non descriptifs, et ce n’est pas un bon moyen d’expliquer un point
stuartdotnet
@stuartdotnet: Je ne pense pas avoir manqué votre point: vous prétendez que les noms descriptifs sont toujours meilleurs et rendent le code toujours plus lisible et explicite.
Giorgio