Quels sont les avantages de l'injection de dépendance dans les cas où presque tout le monde a besoin d'accéder à une structure de données commune?


20

Il y a beaucoup de raisons pour lesquelles les globaux sont mauvais en POO.

Si le nombre ou la taille des objets à partager est trop important pour être efficacement transmis dans les paramètres de fonction, tout le monde recommande généralement l' injection de dépendances plutôt qu'un objet global.

Cependant, dans le cas où presque tout le monde a besoin de connaître une certaine structure de données, pourquoi l'injection de dépendance est-elle meilleure qu'un objet global?

Exemple (simplifié, pour montrer le point en général, sans plonger trop profondément dans une application spécifique)

Il existe un certain nombre de véhicules virtuels qui ont un grand nombre de propriétés et d'états, du type, du nom, de la couleur, de la vitesse, de la position, etc. Un certain nombre d'utilisateurs peuvent les contrôler à distance, et un grand nombre d'événements (les deux utilisateurs- initiés et automatiques) peuvent changer beaucoup de leurs états ou propriétés.

La solution naïve serait de simplement en faire un conteneur global, comme

vector<Vehicle> vehicles;

qui peut être consulté de n'importe où.

La solution la plus conviviale pour les POO serait que le conteneur soit membre de la classe qui gère la boucle d'événement principale et qu'il soit instancié dans son constructeur. Chaque classe qui en a besoin, et qui est membre du thread principal, aura accès au conteneur via un pointeur dans son constructeur. Par exemple, si un message externe arrive via une connexion réseau, une classe (une pour chaque connexion) gérant l'analyse prendra le relais et l'analyseur aura accès au conteneur via un pointeur ou une référence. Maintenant, si le message analysé entraîne soit un changement dans un élément du conteneur, ou nécessite certaines données pour effectuer une action, il peut être géré sans avoir besoin de lancer des milliers de variables via des signaux et des emplacements (ou pire, les stocker dans l'analyseur pour être récupéré plus tard par celui qui a appelé l'analyseur). Bien sûr, toutes les classes qui reçoivent l'accès au conteneur via l'injection de dépendances font partie du même thread. Différents threads n'y accéderont pas directement, mais feront leur travail, puis enverront des signaux au thread principal, et les emplacements du thread principal mettront à jour le conteneur.

Cependant, si la majorité des classes auront accès au conteneur, qu'est-ce qui le rend vraiment différent d'un global? Si tant de classes ont besoin des données dans le conteneur, la "méthode d'injection de dépendances" n'est-elle pas simplement un global déguisé?

Une réponse serait la sécurité des threads: même si je fais attention à ne pas abuser du conteneur global, peut-être qu'un autre développeur à l'avenir, sous la pression d'une échéance proche, utilisera néanmoins le conteneur global dans un thread différent, sans prendre soin de tout les cas de collision. Cependant, même dans le cas de l'injection de dépendances, on pourrait donner un pointeur à quelqu'un qui s'exécute dans un autre thread, conduisant aux mêmes problèmes.


6
Attendez, vous parlez de globaux non mutables et utilisez un lien vers "pourquoi l'état global est-il mauvais" pour le justifier? WTF?
Telastyn

1
Je ne justifie pas du tout les mondiaux. Je pense qu'il est clair que je suis d'accord avec le fait que les globaux ne sont pas la bonne solution. Je parle juste d'une situation où même l'utilisation de l'injection de dépendance ne peut pas être beaucoup mieux que (ou peut-être même presque impossible à distinguer) des globaux. Donc, une réponse pourrait souligner d'autres avantages cachés de l'utilisation de l'injection de dépendance dans cette situation, ou que d'autres approches seraient meilleures que di
vsz

9
Mais il y a une décidé différence entre lecture seule et globals état global.
Telastyn


1
@vsz pas vraiment - la question concerne les usines de localisation de services comme moyen de contourner le problème de nombreux objets distincts; mais ses réponses répondent également à votre question d'autoriser les classes à accéder aux données globales plutôt qu'aux données globales transmises aux classes.
gbjbaanb

Réponses:


37

dans le cas où presque tout le monde a besoin de connaître une certaine structure de données, pourquoi l'injection de dépendance est-elle meilleure qu'un objet global?

L'injection de dépendance est la meilleure chose depuis le pain tranché , alors que les objets mondiaux sont connus depuis des décennies pour être la source de tous les maux , c'est donc une question plutôt intéressante.

Le point de l'injection de dépendance n'est pas simplement de s'assurer que chaque acteur qui a besoin d'une ressource peut l'avoir, car évidemment, si vous rendez toutes les ressources globales, alors chaque acteur aura accès à toutes les ressources, problème résolu, non?

Le point d'injection de dépendance est:

  1. Permettre aux acteurs d'accéder aux ressources en fonction des besoins , et
  2. Pour contrôler l' instance d'une ressource accessible par un acteur donné.

Le fait que dans votre configuration particulière tous les acteurs aient besoin d'accéder à la même instance de ressource n'est pas pertinent. Croyez-moi, vous aurez un jour besoin de reconfigurer les choses pour que les acteurs aient accès aux différentes instances de la ressource, et alors vous vous rendrez compte que vous vous êtes peint dans un coin. Certaines réponses ont déjà pointé une telle configuration: le test .

Autre exemple: supposez que vous divisez votre application en client-serveur. Tous les acteurs sur le client utilisent le même ensemble de ressources centrales sur le client et tous les acteurs sur le serveur utilisent le même ensemble de ressources centrales sur le serveur. Supposons maintenant, un jour, que vous décidiez de créer une version "autonome" de votre application client-serveur, où le client et le serveur sont empaquetés dans un seul exécutable et exécutés dans la même machine virtuelle. (Ou environnement d'exécution, selon la langue de votre choix.)

Si vous utilisez l'injection de dépendances, vous pouvez facilement vous assurer que tous les acteurs clients reçoivent les instances de ressource client avec lesquelles travailler, tandis que tous les acteurs serveur reçoivent les instances de ressource serveur.

Si vous n'utilisez pas l'injection de dépendances, vous n'avez pas de chance, car une seule instance globale de chaque ressource peut exister dans une machine virtuelle.

Ensuite, vous devez considérer: tous les acteurs ont-ils vraiment besoin d'accéder à cette ressource? vraiment?

Il est possible que vous ayez fait l'erreur de transformer cette ressource en un objet divin, (donc, bien sûr, tout le monde doit y avoir accès) ou peut-être surestimez-vous grossièrement le nombre d'acteurs de votre projet qui ont réellement besoin d' accéder à cette ressource .

Avec les globaux, chaque ligne de code source dans votre application entière a accès à chaque ressource globale unique. Avec l'injection de dépendance, chaque instance de ressource n'est visible que par les acteurs qui en ont réellement besoin. Si les deux sont les mêmes (les acteurs qui ont besoin d'une ressource particulière comprennent 100% des lignes de code source de votre projet), alors vous devez avoir fait une erreur dans votre conception. Donc, soit

  • Transformer cette grande et énorme ressource de dieu en sous-ressources plus petites, de sorte que différents acteurs doivent avoir accès à différentes parties de celle-ci, mais rarement un acteur a besoin de toutes ses pièces, ou

  • Refactorisez vos acteurs pour qu'ils n'acceptent à leur tour que les paramètres des sous-ensembles du problème sur lesquels ils doivent travailler, de sorte qu'ils n'ont pas à consulter en permanence une grande et énorme ressource centrale.


Je ne suis pas sûr de comprendre cela - d'une part, vous dites que DI vous permet d'utiliser plusieurs instances d'une ressource et que les globaux ne le font pas, ce qui est clairement un non-sens à moins que vous ne confondiez une seule variable statique avec plusieurs objets instanciés - il n'y a aucune raison que "le global" doit être soit une seule instance, soit une seule classe.
gbjbaanb du

3
@gbjbaanb Supposons que la ressource soit un Zork. L'OP dit que non seulement chaque objet de son système a besoin d'un Zork pour travailler avec, mais aussi, qu'un seul Zork existera jamais, et que tous ses objets n'auront besoin d'accéder qu'à cette seule instance de Zork. Je dis qu'aucune de ces deux hypothèses n'est raisonnable: il n'est probablement pas vrai que tous ses objets ont besoin d'un Zork, et il y aura des moments où certains objets auront besoin d'une certaine instance de Zork, tandis que d'autres objets auront besoin d'un instance différente de Zork.
Mike Nakis

2
@gbjbaanb Je ne suppose pas que vous me demandiez d'expliquer pourquoi il serait stupide d'avoir deux instances globales de Zork, de les nommer ZorkA et ZorkB, et de coder en dur les objets que l'on utilisera ZorkA et que l'on utilisera ZorkB, droite?
Mike Nakis

1
Je n'ai pas dit tout le monde, j'ai dit presque tout le monde. Le point de la question était de savoir si DI a d'autres avantages en plus de refuser l'accès aux quelques acteurs qui n'en ont pas besoin. Je sais que les «objets divins» sont un anti-modèle, mais suivre aveuglément les meilleures pratiques peut aussi en être un. Il peut arriver que le but d'un programme soit de faire diverses choses avec une certaine ressource, dans ce cas, presque tout le monde a besoin d'accéder à cette ressource. Un bon exemple serait un programme qui fonctionne sur une image: presque tout ce qu'il contient a quelque chose à voir avec les données d'image.
vsz

1
@gbjbaanb Je pense que l'idée est d'avoir une classe client capable de travailler exclusivement sur ZorkA ou ZorkB comme indiqué, et non de laisser la classe client décider laquelle choisir.
Eugene Ryabtsev

39

Il existe de nombreuses raisons pour lesquelles les globaux non mutables sont mauvais dans la POO.

C'est une affirmation douteuse. Le lien que vous utilisez comme preuve fait référence à l' état - mutable globals. Ils sont décidément mauvais. Les globales en lecture seule ne sont que des constantes. Les constantes sont relativement saines. Je veux dire, vous n'allez pas injecter la valeur de pi dans toutes vos classes, n'est-ce pas?

Si le nombre ou la taille des objets nécessitant un partage est trop grand pour être efficacement transmis dans les paramètres de fonction, tout le monde recommande généralement l'injection de dépendances plutôt qu'un objet global.

Non, ils ne le font pas.

Si vos fonctions / classes ont trop de dépendances, les gens recommandent de s'arrêter et de voir pourquoi votre conception est si mauvaise. Avoir une cargaison de dépendances est un signe que votre classe / fonction fait probablement trop de choses et / ou que vous n'avez pas d'abstraction suffisante.

Il existe un certain nombre de véhicules virtuels qui ont un grand nombre de propriétés et d'états, du type, du nom, de la couleur, de la vitesse, de la position, etc. Un certain nombre d'utilisateurs peuvent les contrôler à distance, et un grand nombre d'événements (les deux utilisateurs- initiés et automatiques) peuvent changer beaucoup de leurs états ou propriétés.

Ceci est un cauchemar du design. Vous avez un tas de choses qui peuvent être utilisées / abusées sans aucun moyen de validation, aucune manière de limitations de concurrence - c'est juste un couplage partout.

Cependant, si la majorité des classes auront accès au conteneur, qu'est-ce qui le rend vraiment différent d'un global? Si tant de classes ont besoin des données dans le conteneur, la "méthode d'injection de dépendances" n'est-elle pas simplement un global déguisé?

Oui, avoir un couplage avec des données communes partout n'est pas trop différent d'un monde, et en tant que tel, c'est toujours mauvais.

L'avantage est que vous dissociez les consommateurs de la variable globale elle-même. Cela vous offre une certaine flexibilité dans la façon dont l'instance est créée. Cela ne vous oblige pas non plus à avoir une seule instance. Vous pouvez en faire différents à transmettre à vos différents consommateurs. Vous pouvez également créer différentes implémentations de votre interface par consommateur si vous en avez besoin.

Et en raison du changement qui vient inévitablement avec les exigences changeantes des logiciels, un tel niveau de base de flexibilité est vital .


désolé pour la confusion avec "non-mutable", je voulais dire mutable, et d'une manière ou d'une autre je n'ai pas reconnu mon erreur.
vsz

"Ceci est un cauchemar du design" - je le sais. Mais si les exigences sont que tous ces événements doivent pouvoir effectuer les changements dans les données, alors les événements seront couplés aux données même si je les divise et les cache sous une couche d'abstractions. L'ensemble du programme concerne ces données. Par exemple, si un programme doit effectuer un grand nombre d'opérations différentes sur une image, toutes ou presque toutes ses classes seront en quelque sorte couplées aux données d'image. Dire simplement "écrivons un programme qui fait quelque chose de différent" n'est pas acceptable.
vsz

2
Les applications qui ont de nombreux objets qui accèdent régulièrement à certaines bases de données ne sont pas exactement sans précédent.
Robert Harvey

@RobertHarvey - bien sûr, mais l'interface dont ils dépendent "peut-elle accéder à une base de données" ou "un magasin de données persistant pour foos"?
Telastyn

1
Ça me rappelle un Dilbert où PHB demande 500 fonctionnalités et Dilbert dit non. PHB demande donc 1 fonctionnalité et Dilbert dit que oui. Le lendemain, Dilbert reçoit 500 demandes chacune pour 1 fonctionnalité. Si quelque chose ne se modifie pas, il ne se modifie pas, peu importe comment vous l'habillez.
corsiKa

16

Il y a trois raisons principales à considérer.

  1. Lisibilité. Si chaque unité de code a tout ce dont elle a besoin pour être injectée ou transmise en tant que paramètre, il est facile de regarder le code et de voir instantanément ce qu'il fait. Cela vous donne une localité de fonction, ce qui vous permet également de mieux séparer les préoccupations et vous oblige également à réfléchir ...
  2. Modularité. Pourquoi l'analyseur devrait-il connaître la liste complète des véhicules et toutes leurs propriétés? Ce n'est probablement pas le cas. Il doit peut-être se demander si un identifiant de véhicule existe ou si le véhicule X a la propriété Y. Très bien, cela signifie que vous pouvez écrire un service qui fait cela et injecter uniquement ce service dans votre analyseur. Tout à coup, vous vous retrouvez avec une conception qui a beaucoup plus de sens, chaque bit de code ne gérant que les données qui lui sont pertinentes. Ce qui nous amène à ...
  3. Testabilité. Pour un test unitaire typique, vous souhaitez configurer votre environnement pour de nombreux scénarios différents et l'injection le rend très facile à mettre en œuvre. Encore une fois, pour revenir à l'exemple de l'analyseur, voulez-vous vraiment créer à la main une liste complète de véhicules à part entière pour chaque scénario de test que vous écrivez pour votre analyseur? Ou voudriez-vous simplement créer une implémentation fictive de l'objet de service susmentionné, renvoyant un nombre variable d'ID? Je sais lequel je choisirais.

Donc, pour résumer: DI n'est pas un objectif, c'est juste un outil qui permet d'atteindre un objectif. Si vous l'utilisez à mauvais escient (comme il semble que vous le fassiez dans votre exemple), vous ne vous rapprocherez pas du but, il n'y a aucune propriété magique de DI qui rendra une mauvaise conception bonne.


+1 pour une réponse concise axée sur les problèmes de maintenance. Plus de réponses P: SE devraient être comme celle-ci.
dodgethesteamroller

1
Votre résumé est si bon. Tellement bon. Si vous pensez que [modèle de conception du mois] ou [mot à la mode du mois] résoudra comme par magie vos problèmes, vous allez vous amuser.
corsiKa

@corsiKa J'étais opposé à DI (ou Spring, pour être plus précis) pendant longtemps parce que je ne pouvais pas voir le point. Cela ressemblait à énormément de tracas sans gain tangible (c'était à l'époque de la configuration XML). J'aimerais avoir trouvé de la documentation sur ce que DI vous achète à l'époque. Mais je ne l'ai pas fait, j'ai donc dû le découvrir moi-même. Ça a pris du temps. :)
biziclop

3

C'est vraiment une question de testabilité - normalement, un objet global est très maintenable et facile à lire / comprendre (une fois que vous le savez, bien sûr) mais c'est un élément permanent et lorsque vous venez de diviser vos classes en tests isolés, vous constatez que votre l'objet global est bloqué en disant "qu'en est-il de moi" et donc vos tests isolés nécessitent que l'objet global y soit inclus. Cela n'aide pas que la plupart des frameworks de test ne prennent pas facilement en charge ce scénario.

Alors oui, c'est toujours un mondial. La raison fondamentale pour l'avoir n'a pas changé, seulement la manière dont il est accédé.

Quant à l'initialisation, c'est toujours un problème - vous devez toujours définir la configuration pour que vos tests puissent s'exécuter, donc vous n'y gagnez rien. Je suppose que vous pouvez diviser un grand objet dépendant en plusieurs plus petits et passer celui qui convient aux classes qui en ont besoin, mais vous obtenez probablement encore plus de complexité pour l'emporter sur les avantages.

Indépendamment de tout cela, c'est un exemple de la `` queue qui remue le chien '', la nécessité de tester conduit à la façon dont la base de code est architecturée (des problèmes similaires se posent avec des éléments tels que les classes statiques, par exemple la date / heure actuelle).

Personnellement, je préfère coller toutes mes données globales dans un objet global (ou plusieurs) mais fournir des accesseurs pour obtenir les bits dont mes classes ont besoin. Ensuite, je peux me moquer de ceux-ci pour retourner les données que je veux quand il s'agit de tester sans la complexité supplémentaire de DI.


"normalement un objet global est très maintenable" - pas s'il est mutable. D'après mon expérience, avoir autant d'état global mutable peut être un cauchemar (bien qu'il existe des moyens de le rendre supportable).
sleske

3

En plus des réponses que vous avez déjà,

Il existe un certain nombre de véhicules virtuels qui ont un grand nombre de propriétés et d'états, du type, du nom, de la couleur, de la vitesse, de la position, etc. Un certain nombre d'utilisateurs peuvent les contrôler à distance, et un grand nombre d'événements (les deux utilisateurs- initiés et automatiques) peuvent changer beaucoup de leurs états ou propriétés.

L'une des caractéristiques de la programmation OOP est de ne conserver que les propriétés dont vous avez vraiment besoin pour un objet spécifique,

MAINTENANT SI votre objet a trop de propriétés - vous devez décomposer davantage votre objet en sous-objets.

Éditer

Déjà mentionné pourquoi les objets globaux sont mauvais, j'y ajouterais un exemple.

Vous devez faire des tests unitaires sur le code GUI d'un formulaire Windows, si vous utilisez global, vous devrez créer tous les boutons et ses événements par exemple pour créer un cas de test approprié ...


Certes, mais par exemple, l'analyseur devra tout savoir car toute commande pouvant être reçue peut apporter des modifications à n'importe quoi.
vsz

1
"Avant d'en venir à votre vraie question" ... allez-vous répondre à sa question?
gbjbaanb

@gbjbaanb question a beaucoup de texte, s'il vous plaît laissez-moi un peu de temps pour le lire correctement
Mathématiques
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.