Quelle est la meilleure définition de l'injection de dépendance?

10

Chaque fois que quelqu'un me rejoint et me demande de définir l'injection de dépendance de manière conceptuelle et d'expliquer les avantages et les inconvénients réels de l'utilisation de la DI dans la conception de logiciels. J'avoue que j'ai quelques difficultés à expliquer les concepts de DI. Chaque fois que je dois leur raconter l'histoire du principe de la responsabilité unique, de la composition sur l'héritage, etc.

N'importe qui peut m'aider à expliquer la meilleure façon de décrire l'ID pour les développeurs?

Tiago Sampaio
la source
2
Le défi ici est qu'il y a tellement de définitions contradictoires de DI. Je prends la position "pure DI": si j'ai une fonction qui s'appuie sur ses paramètres pour fournir tous les états, données, etc., alors cette fonction utilise DI. À l'autre extrême, certains diront que sans cadre DI, il n'y a pas d'injection de dépendances (bien qu'ils se trompent bien sûr;)). Donc, à moins que vous ne puissiez définir une définition, vous ne pouvez pas commencer à expliquer de quoi il s'agit ...
David Arno
Donc, si je comprends bien, ce n'est pas seulement un de mes problèmes.
Tiago Sampaio
Tout se résume à ceci: l'injection de dépendance est une technique utilisée pour réaliser l'inversion de dépendance; tout le reste est juste des trucs supplémentaires construits en plus de cela. Notez que dans ces deux termes, le mot «dépendance» a des significations légèrement différentes. Dans l'injection de dépendances, il fait référence au composant dont dépend le code. Dans l'inversion de dépendance, il se réfère à la relation (dirigée) elle-même - celle que nous voulons inverser. Ce dernier est l'objectif, donc les principaux avantages et inconvénients sont les mêmes; ainsi que certaines préoccupations supplémentaires liées à la mise en œuvre réelle, comme la gestion de la durée de vie des objets.
Filip Milovanović

Réponses:

22

L'injection de dépendance est un nom horrible (OMI) 1 pour un concept plutôt simple. Voici un exemple:

  1. Vous avez une méthode (ou une classe avec des méthodes) qui fait X (par exemple récupérer des données de la base de données)
  2. Dans le cadre de l'exécution de X, ladite méthode crée et gère une ressource interne (par exemple a DbContext). Cette ressource interne est ce qu'on appelle une dépendance
  3. Vous supprimez la création et la gestion de la ressource (c.-à-d. DbContext) De la méthode et faites en sorte qu'il incombe à l'appelant de fournir cette ressource (en tant que paramètre de méthode ou lors de l'instanciation de la classe)
  4. Vous faites maintenant l'injection de dépendance.


[1] : Je viens d'un milieu de niveau inférieur et il m'a fallu des mois pour m'asseoir et apprendre l'injection de dépendance parce que le nom implique que ce serait quelque chose de beaucoup plus compliqué, comme l' injection de DLL . Le fait que Visual Studio (et nous les développeurs en général) se réfère aux bibliothèques .NET (DLL ou assemblys ) dont dépend un projet en tant que dépendances n'aide pas du tout. Il existe même une chose telle que le Dependency Walker (depend.exe) .


[Modifier] J'ai pensé qu'un code de démonstration serait utile pour certains, alors en voici un (en C #).

Sans injection de dépendance:

public class Repository : IDisposable
{
    protected DbContext Context { get; }

    public Repository()
    {
        Context = new DbContext("name=MyEntities");
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}

Votre consommateur ferait alors quelque chose comme:

using ( var repository = new Repository() )
{
    // work
}

La même classe implémentée avec un modèle d'injection de dépendance serait comme ceci:

public class RepositoryWithDI
{
    protected DbContext Context { get; }

    public RepositoryWithDI(DbContext context)
    {
        Context = context;
    }
}

C'est maintenant la responsabilité de l'appelant d'instancier un DbContextet de le transmettre (errm, injecter ) à votre classe:

using ( var context = new DbContext("name=MyEntities") )
{
    var repository = new RepositoryWithDI(context);

    // work
}
Marc.2377
la source
3
Cela devrait être ajouté à Wikipedia.
Evorlor
2
C'est maintenant la responsabilité de l'appelant d'instancier un DbContext - je pense que ce serait la responsabilité du point d'entrée de l'application d'instancier toutes les dépendances requises. Le consommateur n'a donc qu'à introduire le type requis comme dépendance dans son propre contrat.
Fabio
@Fabio C'est possible. (Dans ce cas, la responsabilité de l'appelant serait de fournir la ressource qui a été instanciée au démarrage de l'application à la méthode / classe appelée.) Dans mon exemple, ce n'est pas le cas, car ce n'est pas une obligation d'expliquer le concept d'injection de dépendance .
Marc.2377
5

Les concepts abstraits sont souvent mieux expliqués en utilisant une analogie du monde réel. Voici mon analogie préférée:

Vous dirigez une sandwicherie. Vous faites des sandwichs incroyables, mais vous ne savez pas grand-chose sur le pain lui-même. Vous n'avez que du pain blanc fade. Votre travail se concentre entièrement sur les garnitures que vous utilisez pour transformer le pain en sandwich.

Cependant, certains de vos clients préféreraient vraiment le pain brun. Certains préféreraient le grain entier. Vous ne vous souciez pas vraiment de toute façon, vous pouvez faire un sandwich incroyable tant que c'est un pain de taille similaire. Vous ne voulez pas non plus avoir à assumer la responsabilité supplémentaire d'acheter plusieurs types de pain et de maintenir les stocks. Même si vous stockez plusieurs types de pain, il y aura toujours un client au goût exotique que vous ne pouviez pas raisonnablement prévoir.

Vous instituez donc une nouvelle règle: les clients apportent leur pain. Vous ne fournissez plus de pain vous-même. C'est une situation gagnant-gagnant: les clients ont le pain exact qu'ils veulent et vous n'avez plus à vous soucier de vous procurer le pain qui ne vous intéresse pas. Après tout, vous êtes un fabricant de sandwich, pas un boulanger.

Oh, et pour accueillir les clients qui ne veulent pas acheter leur propre pain, vous ouvrez une deuxième boutique à côté qui vend vos pains blancs fades originaux. Les clients qui n'ont pas apporté leur propre pain doivent simplement obtenir le pain par défaut, puis venir chez vous pour faire un sandwich avec.

Ce n'est pas parfait, mais il met en évidence la caractéristique clé: donner le contrôle au consommateur . Le gagnant-gagnant inhérent est que vous n'avez plus à acquérir vos propres dépendances, et votre consommateur n'est pas gêné dans son choix de dépendance.

Flater
la source
1
J'aime ça, mais l'OP cherche une explication pour les développeurs . Une abstraction initiale est bonne, mais tôt ou tard, elle nécessiterait un exemple réel.
Robbie Dee
1
@RobbieDee: Lorsque l' objectif du modèle est clair, sa mise en œuvre a tendance à devenir claire également. Par exemple, la réponse de Marc est absolument correcte, mais j'ai l'impression que l'explication s'enlise dans la nature complexe de l'exemple de situation qu'il utilise. Cela se résume à « Si vous voulez construire un bateau, ne pas affecter tambour gens à bois Collect et ne pas les tâches et le travail, mais plutôt leur apprendre à long pour l'immensité infinie de la mer. » . Plutôt que d'expliquer quoi faire, je préfère expliquer pourquoi le faire.
Flater
2
Vous avez bien sûr raison, mais je ne peux pas m'empêcher de penser que j'aurais besoin d'un exemple tangible - comme ne pas avoir de véritable système de fichiers ou base de données pour me mettre en appétit, mais peut-être que c'est juste ma vision étroite du développeur :)
Robbie Dee
1

Réponse simple à cela:

Tout d'abord, une classe doit avoir une responsabilité bien définie, et tout ce qui est en dehors de cette portée doit être gardé en dehors de cette classe. Cela dit, l'injection de dépendances consiste à injecter une fonctionnalité d'une autre classe B dans une classe A en utilisant l'aide d'un "tiers" pour réaliser cette séparation des préoccupations, aidant la classe A à terminer une opération qui est hors de sa portée.

.Net Core est un assez bon exemple que vous pouvez donner car ce framework utilise beaucoup d'injection de dépendances. Généralement, les services que vous souhaitez injecter se trouvent dans le startup.csfichier.

Bien sûr, l'étudiant doit être au courant de certains concepts comme le polymorphisme, les interfaces et les principes de conception OOP.

Fernando Calazans
la source
0

Il y a beaucoup de peluches et de bunkum autour de ce qui est, par essence, un concept simple.

Il est également très facile de s'embourber avec " quel cadre je dois utiliser " quand vous pouvez le faire tout simplement en code.

Voici la définition que j'utilise personnellement:

Étant donné le comportement X avec une dépendance de Y. L'injection de dépendance implique la possibilité de fournir tout Y qui satisfait aux critères pour être une instance de Y, même si vous n'en avez pas.

Certains exemples peuvent être où Y est un système de fichiers ou une connexion à une base de données.

Des cadres tels que moq permettent de définir des doubles (versions prétendues de Y) à l'aide d'une interface , il est donc possible d'injecter dans une instance de Y, où Y est par exemple, une connexion à une base de données.

Il est facile de tomber dans le piège de croire qu'il s'agit purement d'un problème de test unitaire, mais c'est un modèle très utile pour tout morceau de code où un changement est attendu et, sans doute, est une bonne pratique de toute façon.

Robbie Dee
la source
0

Nous fournissons le comportement d'une fonction au moment de l'exécution grâce à la méthode d'insertion de ce comportement dans la fonction via un paramètre.

Le modèle de stratégie est un excellent exemple d'injection de dépendance.

ShinEmperor
la source
0

Pour ce faire correctement, nous devons d'abord définir les dépendances et l'injection.

  • Dépendance: toute ressource dont une opération a besoin.
  • Injection: passage de cette ressource à l'opération, généralement comme argument d'une méthode.

Un exemple rudimentaire serait une méthode qui ajoute deux valeurs. De toute évidence, cette méthode nécessite l'ajout de valeurs. S'ils sont fournis en les passant comme arguments, ce serait déjà un cas d'injection de dépendance. L'alternative serait d'implémenter les opérandes en tant que propriétés ou variables globales. De cette façon, aucune dépendance ne serait injectée, les dépendances seraient disponibles à l'extérieur en amont.

Supposons que vous utilisiez des propriétés à la place et que vous les nommiez A et B. Si vous modifiez les noms en Op1 et Op2, vous rompriez la méthode Add. Ou votre IDE mettrait à jour tous les noms pour vous, le fait est que la méthode devrait également être mise à jour car elle a des dépendances avec des ressources externes.

Cet exemple est basique mais vous pouvez imaginer des exemples plus complexes où la méthode effectue une opération sur un objet comme une image ou où elle lit à partir d'un flux de fichiers. Voulez-vous que la méthode atteigne l'image, exigeant qu'elle sache où elle se trouve? Non. Voulez-vous que la méthode ouvre le fichier lui-même, lui demandant de savoir où chercher le fichier ou même de savoir qu'il lira un fichier? Non.

Le point: réduire la fonctionnalité d'une méthode à son comportement de base et dissocier la méthode de son environnement. Vous obtenez le premier en faisant le second, vous pouvez considérer cela comme la définition de l'injection de dépendance.

Les avantages: puisque les dépendances de l'environnement de la méthode ont été éliminées, les modifications apportées à la méthode n'auront pas d'impact sur l'environnement et vice versa. => L'application devient plus facile à maintenir (modifier).

Martin Maat
la source