Pourquoi devrais-je utiliser l'injection de dépendance?

95

J'ai du mal à chercher des ressources sur les raisons pour lesquelles je devrais utiliser l' injection de dépendance . La plupart des ressources que je vois expliquent qu’elle passe simplement une instance d’un objet à une autre instance d’un objet, mais pourquoi? S'agit-il uniquement d'une architecture / d'un code plus propres ou cela affecte-t-il les performances dans son ensemble?

Pourquoi devrais-je faire ce qui suit?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Au lieu de ce qui suit?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}
Daniel
la source
8
Vous introduisez une dépendance codée en dur dans le paramètre deactivateProfile () (qui est incorrect). Vous avez plus de code découplé dans le premier, ce qui facilite le changement et le test.
Aulis Ronkainen
3
Pourquoi voudriez-vous faire le premier? Vous transmettez un paramètre et ignorez ensuite sa valeur.
Phil N DeBlanc
57
Je ne suis pas d'accord avec les votes négatifs. Bien que le sujet puisse être considéré comme trivial pour les experts, la question est intéressante: si l’inversion de dépendance doit être utilisée, elle doit être justifiée.
Flater
13
@PhilNDeBlanc: Ce code est clairement trop simplifié et n'indique pas vraiment la logique du monde réel. Cependant, deactivateProfileme suggère que définir la valeur isActivefalse sans prendre en compte son état antérieur est la bonne approche à adopter ici. Appeler la méthode de manière inhérente signifie que vous voulez la définir comme inactive et non pour obtenir son statut actif (in).
Flater
2
Votre code n'est pas un exemple d'injection de dépendance ou d'inversion. C'est un exemple de paramétrage (qui est souvent bien meilleur que DI).
jpmc26

Réponses:

104

L’avantage est que, sans injection de dépendance, votre classe de profil

  • a besoin de savoir comment créer un objet Settings (enfreint le principe de responsabilité unique)
  • Crée toujours son objet Settings de la même manière (crée un couplage étroit entre les deux)

Mais avec l'injection de dépendance

  • La logique pour créer des objets de paramètres est ailleurs
  • Il est facile d'utiliser différents types d'objets Paramètres

Cela peut sembler (ou même être) sans importance dans ce cas particulier, mais imaginons qu'il ne s'agisse pas d'un objet Settings, mais d'un objet DataStore, qui peut avoir différentes implémentations, une qui stocke les données dans des fichiers et une autre qui les stocke dans une base de données. Et pour les tests automatisés, vous souhaitez également une implémentation factice. Maintenant, vous ne voulez vraiment pas que la classe Profile définisse le code utilisé - et plus important encore, vous ne voulez vraiment pas que la classe Profile connaisse les chemins du système de fichiers, les connexions à la base de données et les mots de passe, de sorte que la création d'objets DataStore doit se passer ailleurs.

Michael Borgwardt
la source
23
This may seem (or even be) irrelevant in this particular caseJe pense que c'est très pertinent, en fait. Comment obtiendriez-vous les paramètres? Un grand nombre de systèmes que j'ai vus auront un ensemble de paramètres par défaut codé en dur et une configuration pour le public. Vous devez donc charger les deux et remplacer certaines valeurs par les paramètres publics. Vous pouvez même avoir besoin de plusieurs sources de valeurs par défaut. Peut-être en obtiendrez-vous même certains sur disque, d'autres sur DB. Ainsi, toute la logique d'obtention de paramètres peut, et est souvent, non-triviale - ce n'est certainement pas quelque chose qui consomme du code qui devrait ou qui importerait.
VLAZ
Nous pourrions également mentionner que l'initialisation d'objet pour un composant non trivial, tel qu'un service Web, rendrait $setting = new Setting();terriblement inefficace. L'injection et l'instanciation d'objet se produisent une fois.
vikingsteve
9
Je pense que l'utilisation de simulacres pour les tests devrait avoir plus d'importance. Si vous ne regardez que le code, il y a de fortes chances qu'il s'agisse toujours d'un objet Paramètres et qu'il ne changera jamais. Il semble donc inutile de le transmettre. Cependant, la première fois que vous essayez de tester un objet profil par lui - même sans avoir besoin aussi un objet Paramètres ( en utilisant un objet fantaisie plutôt comme une solution) le besoin est très apparent.
JPhi1618
2
@ JPhi1618 Je pense que le problème en soulignant "DI est pour les tests unitaires" est que cela conduit à la question "pourquoi ai-je besoin de tests unitaires". La réponse peut sembler évidente pour vous, et les avantages sont bel et bien là, mais pour quelqu'un qui commence à peine, en disant "vous devez faire ce truc compliqué pour pouvoir faire cet autre truc compliqué" a tendance à être un peu un éteindre. Il est donc bon de mentionner différents avantages qui pourraient être plus applicables à ce qu’ils font en ce moment.
IMSoP
1
@ user986730 - c'est un très bon point, et souligne que le principe de base est l' inversion de dépendance , dont l'injection n'est qu'une technique.Par exemple, en C, vous ne pouvez pas vraiment injecter de dépendances à l'aide d'une bibliothèque IOC, mais vous pouvez invertissez-les au moment de la compilation en incluant différents fichiers source pour vos modèles, etc., ce qui a le même effet.
Stephen Byrne
64

L'injection de dépendance rend votre code plus facile à tester.

J'ai appris cela directement lorsque j'ai été chargé de corriger un bogue difficile à détecter dans l'intégration PayPal de Magento.

Un problème se posait lorsque PayPal informait Magento d'un paiement ayant échoué: Magento n'enregistrerait pas correctement l'échec.

Il serait très fastidieux de tester un correctif potentiel "manuellement": vous devriez en quelque sorte déclencher une notification PayPal "Échec". Vous devez soumettre un chèque électronique, l'annuler et attendre qu'il soit erroné. Cela signifie plus de 3 jours pour tester un changement de code à un caractère!

Heureusement, il semble que les principaux développeurs de Magento qui ont développé cette fonction avaient à l’esprit des tests et utilisaient un modèle d’injection de dépendance pour la rendre triviale. Cela nous permet de vérifier notre travail avec un cas de test simple comme celui-ci:

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

Je suis sûr que le modèle DI présente de nombreux autres avantages, mais une plus grande facilité de test est le principal avantage de mon esprit .

Si vous êtes curieux de connaître la solution à ce problème, consultez le référentiel GitHub ici: https://github.com/bubbleupdev/BUCorefix_Paypalstatus

Eric Seastrand
la source
4
L'injection de dépendances rend le code plus facile à tester que celui comportant des dépendances codées en dur. Éliminer les dépendances de la logique métier est encore mieux.
Ant P
1
Et un moyen important de faire ce que @AntP suggère est le paramétrage . La logique permettant de transformer les résultats issus de la base de données en objet utilisé pour remplir un modèle de page (communément appelé "modèle") ne devrait même pas savoir qu’une extraction a lieu; il faut juste que ces objets soient entrés.
jpmc26
4
@ jpmc26 en effet - j'ai tendance à parler du noyau fonctionnel, du shell impératif , qui n'est en réalité qu'un nom de fantaisie pour transmettre des données à votre domaine au lieu d'y injecter des dépendances. Le domaine est pur, peut être testé avec des unités sans réplique, puis est simplement enveloppé dans un shell qui adapte des éléments comme la persistance, la messagerie, etc.
Ant P
1
Je pense que le seul intérêt de la testabilité nuit à l’adoption du DI. Cela rend les choses peu attrayantes pour les personnes qui ont l’impression de ne pas avoir besoin de beaucoup d’essais, ou de penser que leurs essais sont déjà sous contrôle. Je dirais qu'il est pratiquement impossible d'écrire du code propre et réutilisable sans DI. Les tests sont très loin sur la liste des avantages et il est décevant de voir cette réponse classée au premier ou au deuxième rang de chaque question sur les avantages de DI.
Carl Leth
26

Pourquoi (quel est même le problème)?

Pourquoi devrais-je utiliser l'injection de dépendance?

Le meilleur mnémonique que j'ai trouvé pour cela est " new is glue ": chaque fois que vous utilisez newdans votre code, ce code est lié à cette implémentation spécifique . Si vous utilisez de manière répétée new in in constructors, vous allez créer une chaîne d'implémentations spécifiques . Et comme vous ne pouvez pas "avoir" une instance d'une classe sans la construire, vous ne pouvez pas séparer cette chaîne.

Par exemple, imaginez que vous écrivez un jeu vidéo de voiture de course. Vous avez commencé avec une classe Game, qui crée un RaceTrack, qui en crée 8 Cars, qui créent chacune un Motor. Maintenant, si vous voulez 4 supplémentaires Carsavec une accélération différente, vous devrez changer chaque classe mentionnée , sauf peut-être Game.

Code plus propre

Est-ce juste pour une architecture / code plus propre

Oui .

Cependant, cela peut sembler moins clair dans cette situation, car c’est plus un exemple de la façon de le faire . L'avantage réel ne s'affiche que lorsque plusieurs classes sont impliquées et est plus difficile à démontrer, mais imaginons que vous ayez utilisé DI dans l'exemple précédent. Le code créant toutes ces choses pourrait ressembler à ceci:

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

L'ajout de ces 4 voitures différentes peut maintenant être fait en ajoutant ces lignes:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • Aucun changement dans RaceTrack, Game, Car, ou Motorétaient nécessaires - ce qui signifie que nous pouvons être sûrs à 100% que nous n'introduisons de nouveaux insectes là - bas!
  • Au lieu de devoir naviguer entre plusieurs fichiers, vous pourrez voir le changement complet sur votre écran en même temps. Cela est dû au fait que la création, l’installation et la configuration sont considérées comme une responsabilité propre . Ce n’est pas à une voiture de construire un moteur.

Considérations de performance

ou cela affecte-t-il la performance dans son ensemble?

Non . Mais pour être tout à fait honnête avec vous, ça pourrait.

Cependant, même dans ce cas, c'est une quantité tellement ridicule que vous n'avez pas besoin de vous en soucier. Si à l'avenir, vous devez écrire du code pour un tamagotchi avec l'équivalent de 5 Mhz de processeur et de 2 Mo de RAM, vous devrez peut- être vous en soucier.

Dans 99,999% * des cas, les performances seront meilleures, car vous avez passé moins de temps à corriger les bogues et plus de temps à l'amélioration de vos algorithmes gourmands en ressources.

* numéro entièrement composé

Informations ajoutées: "codé en dur"

Ne vous méprenez pas, c'est encore très « codées en dur » - les numéros sont écrits directement dans le code. Ne pas coder en dur voudrait dire quelque chose comme stocker ces valeurs dans un fichier texte - par exemple au format JSON - et ensuite les lire à partir de ce fichier.

Pour ce faire, vous devez ajouter du code permettant de lire un fichier, puis d'analyser JSON. Si vous considérez à nouveau l'exemple, dans la version non-DI, un Carou un Motordoit maintenant lire un fichier. Cela ne semble pas trop logique.

Dans la version DI, vous l'ajouteriez au code configurant le jeu.

R. Schmitz
la source
3
Ad-codé en dur, il n'y a pas vraiment beaucoup de différence entre le code et le fichier de configuration. Un fichier fourni avec l'application est une source même si vous lisez de manière dynamique. Extraire des valeurs du code dans des fichiers de données au format JSON ou quel que soit le format de "configuration" n'ajoute rien, à moins que les valeurs ne soient remplacées par l'utilisateur ou dépendent de l'environnement.
Jan Hudec
3
En fait, j'ai déjà fait un Tamagotchi sur un Arduino (16 MHz, 2 Ko)
Jungkook
@ JanHudec True. J'avais en fait une explication plus longue là-bas, mais j'ai décidé de l'enlever pour la raccourcir et de se concentrer sur son rapport avec l'ID. Il y a plus de choses qui ne sont pas correctes à 100%; Globalement, la réponse est plus optimisée pour pousser "le point" de DI sans que cela devienne trop long. Autrement dit, c’est ce que j’aurais voulu entendre lorsque j’ai commencé à travailler avec DI.
R. Schmitz
17

J'ai toujours été dérouté par l'injection de dépendance. Il semblait n'exister que dans les sphères de Java, mais ces sphères en parlaient avec beaucoup de respect. C'était l'un des grands modèles, voyez-vous, qui sont censés mettre de l'ordre dans le chaos. Mais les exemples ont toujours été compliqués et artificiels, établissant un non-problème et cherchant ensuite à le résoudre en rendant le code plus compliqué.

C’était plus logique quand un collègue de Python me transmettait cette sagesse: c’est juste passer des arguments à des fonctions. C'est à peine un motif du tout; plus comme un rappel que vous pouvez demander quelque chose comme argument, même si vous auriez pu éventuellement fournir une valeur raisonnable vous-même.

Votre question est donc à peu près équivalente à "pourquoi ma fonction devrait-elle prendre des arguments?" et a beaucoup des mêmes réponses, à savoir: laisser l'appelant prendre des décisions .

Cela vient avec un coût, bien sûr, parce que maintenant vous forcer l'appelant à prendre une décision ( à moins que vous faites l'argument optionnel), et l'interface est un peu plus complexe. En échange, vous gagnez en flexibilité.

Donc: Y a-t-il une bonne raison pour laquelle vous avez spécifiquement besoin d'utiliser ce Settingtype / valeur en particulier? Existe-t-il une bonne raison pour que le code d’appel demande un autre Settingtype / valeur? (Rappelez-vous, les tests sont du code !)

Eevee
la source
2
Oui. IoC a finalement cliqué sur moi lorsque j'ai réalisé qu'il s'agissait simplement de "laisser l'appelant prendre des décisions". Cette IoC signifie que le contrôle du composant est transféré de l'auteur du composant à l'utilisateur du composant. Et comme à ce moment-là, j'ai déjà eu assez de problèmes avec un logiciel qui se croyait plus intelligent que moi, je me suis immédiatement vendu à l'approche DI.
Joker_vD
1
"Il ne s'agit que de passer des arguments à des fonctions. C'est à peine un modèle." C'est la seule explication bonne ou même compréhensible de DI que j'ai entendue (enfin, la plupart des gens ne parlent même pas de ce que c'est , mais plutôt de ses avantages). Et ensuite, ils utilisent un jargon compliqué comme "inversion de contrôle" et "couplage lâche" qui nécessitent simplement plus de questions pour comprendre quand il s'agit d'une idée vraiment simple.
addison
7

L'exemple que vous donnez n'est pas l'injection de dépendance au sens classique. L'injection de dépendance fait généralement référence à la transmission d'objets dans un constructeur ou à l'utilisation de "l'injection de setter" juste après la création de l'objet, afin de définir une valeur sur un champ dans un objet nouvellement créé.

Votre exemple passe un objet en tant qu'argument à une méthode d'instance. Cette méthode d'instance modifie ensuite un champ sur cet objet. Injection de dépendance? Non. Casser l'encapsulation et la dissimulation de données? Absolument!

Maintenant, si le code était comme ça:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

Ensuite, je dirais que vous utilisez l'injection de dépendance. La différence notable est qu'un Settingsobjet est transmis au constructeur ou à un Profileobjet.

Cela est utile si l'objet Paramètres est coûteux ou complexe à construire, ou si Paramètres est une interface ou une classe abstraite dans laquelle plusieurs implémentations concrètes existent afin de changer le comportement de l'exécution.

Etant donné que vous accédez directement à un champ sur l'objet Settings plutôt que d'appeler une méthode, vous ne pouvez pas tirer parti du polymorphisme, qui est l'un des avantages de l'injection de dépendance.

Il semble que les paramètres d'un profil soient spécifiques à ce profil. Dans ce cas, je ferais l'une des choses suivantes:

  1. Instanciez l'objet Settings dans le constructeur de profils

  2. Transmettez l'objet Settings dans le constructeur et copiez les champs individuels qui s'appliquent au profil

Honnêtement, en passant l'objet Paramètres à deactivateProfile, puis en modifiant un champ interne de l'objet Paramètres, vous obtenez une odeur de code. L'objet Settings doit être le seul à modifier ses champs internes.

Greg Burghardt
la source
5
The example you give is not dependency injection in the classical sense.- Peu importe. Les gens OO ont des objets sur le cerveau, mais vous continuez à donner une dépendance à quelque chose.
Robert Harvey
1
Lorsque vous parlez de « au sens classique », vous êtes, comme le dit @RobertHarvey, un langage purement OO. Dans la programmation fonctionnelle, par exemple, injecter une fonction dans une autre (fonctions d'ordre supérieur) est l'exemple classique d'injection de dépendance de ce paradigme.
David Arno
3
@RobertHarvey: Je suppose que je prenais trop de libertés avec "l'injection de dépendance". L'endroit que les gens pensent le plus souvent au terme et utilisent le terme est en référence aux champs d'un objet "injecté" au moment de la construction de l'objet, ou immédiatement après dans le cas d'une injection de setter.
Greg Burghardt
@DavidArno: Oui, vous avez raison. L'OP semble avoir un extrait de code PHP orienté objet dans la question. Je ne répondais donc qu'à cela, et non pas à la programmation fonctionnelle - bien qu'avec PHP, vous puissiez poser la même question du point de vue de la programmation fonctionnelle.
Greg Burghardt
7

Je sais que je viens en retard à cette fête, mais je sens qu’un point important nous manque.

Pourquoi devrais-je faire ça:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Tu ne devrais pas. Mais pas parce que l'injection de dépendance est une mauvaise idée. C'est parce que c'est mal faire.

Regardons ces choses en utilisant le code. Nous allons faire ceci:

$profile = new Profile();
$profile->deactivateProfile($setting);

quand nous obtenons à peu près la même chose:

$setting->isActive = false; // Deactivate profile

Alors bien sûr, cela semble être une perte de temps. C'est quand vous le faites de cette façon. Ce n'est pas la meilleure utilisation de l'injection de dépendance. Ce n'est même pas la meilleure utilisation d'une classe.

Et si nous avions plutôt ceci:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

Et maintenant, le applicationest libre d'activer et de désactiver le profilesans rien savoir en particulier sur le fait settingque cela change réellement. Pourquoi est-ce bien? Au cas où vous auriez besoin de changer les paramètres. Le système applicationest isolé de ces changements, vous êtes donc libre de vous rendre fou dans un espace confiné sûr sans avoir à tout regarder se briser dès que vous touchez quelque chose.

Ceci suit la construction séparée du principe de comportement . Le modèle DI est simple. Créez tout ce dont vous avez besoin au niveau le plus bas possible, connectez-les ensemble, puis démarrez tous les comportements en un seul appel.

Le résultat est que vous avez un endroit séparé pour décider ce qui est connecté à quoi et un endroit différent pour gérer ce qui dit quoi à quoi que ce soit.

Essayez cela sur quelque chose que vous devez maintenir au fil du temps et voyez si cela ne vous aide pas.

confits_orange
la source
6

En tant que client, lorsque vous engagez un mécanicien pour réparer votre voiture, attendez-vous de ce mécanicien qu'il construise une voiture à partir de rien pour ensuite l'utiliser? Non, vous donnez au mécanicien la voiture sur laquelle vous souhaitez travailler .

En tant que garagiste, quand vous demandez à un mécanicien de faire quelque chose à une voiture, attendez-vous de ce dernier qu'il crée son propre tournevis / clé / pièces de voiture? Non, vous fournissez au mécanicien les pièces / outils dont il a besoin

Pourquoi faisons-nous cela? Eh bien, pensez-y. Vous êtes un garagiste qui veut embaucher quelqu'un pour devenir votre mécanicien. Vous leur apprendrez à être un mécanicien (= vous allez écrire le code).

Ce qui va être plus facile:

  • Enseignez à un mécanicien comment attacher un spoiler à une voiture à l'aide d'un tournevis.
  • Apprenez à un mécanicien à créer une voiture, à créer un spoiler, à créer un tournevis, puis à fixer le spoiler nouvellement créé à la voiture nouvellement créée à l'aide du nouveau tournevis.

Il y a des avantages énormes à ne pas avoir votre mécanicien créer tout à partir de zéro:

  • Évidemment, la formation (= développement) est considérablement raccourcie si vous fournissez simplement à votre mécanicien les outils et pièces existants.
  • Si le même mécanicien doit effectuer le même travail plusieurs fois, vous pouvez vous assurer qu'il réutilise le tournevis au lieu de toujours jeter l'ancien et en créer un nouveau.
  • De plus, un mécanicien qui a appris à tout créer devra être beaucoup plus expert et s'attendre à un salaire plus élevé. L'analogie de codage ici est qu'une classe avec de nombreuses responsabilités est beaucoup plus difficile à maintenir qu'une classe avec une seule responsabilité strictement définie.
  • De plus, lorsque de nouvelles inventions arrivent sur le marché et que les spoilers sont maintenant fabriqués en carbone plutôt qu'en plastique; vous devrez recycler (= réaménager) votre mécanicien expert. Mais votre "simple" mécanicien ne devra pas être recyclé tant que le spoiler pourra toujours être attaché de la même manière.
  • Avoir un mécanicien qui ne repose pas sur une voiture qu'ils ont construite signifie que vous avez un mécanicien capable de gérer n'importe quelle voiture qu'ils peuvent recevoir. Y compris les voitures qui n'existaient même pas encore au moment de la formation du mécanicien. Cependant, votre mécanicien expert ne sera pas en mesure de construire de nouvelles voitures qui ont été créées après leur formation.

Si vous embauchez et que vous formez un mécanicien expert, vous allez vous retrouver avec un employé qui coûte plus cher, prend plus de temps pour effectuer ce qui devrait être un travail simple, et devra sans cesse être recyclé chaque fois qu'une de ses nombreuses responsabilités doit à mettre à jour.

L'analogie de développement est que si vous utilisez des classes avec des dépendances codées en dur, vous allez vous retrouver avec des classes difficiles à maintenir qui nécessiteront un redéveloppement / des modifications continus chaque fois qu'une nouvelle version de l'objet ( Settingsdans votre cas) est créée, et vous-même. Je vais devoir développer une logique interne pour que la classe puisse créer différents types d’ Settingsobjets.

De plus, quiconque consommera votre classe devra également demander à la classe de créer le bon Settingsobjet, au lieu de pouvoir simplement transmettre à la classe tout Settingsobjet qu’elle souhaite transmettre. Cela signifie un développement supplémentaire pour le consommateur afin de comprendre comment demander à la classe de créer le bon outil.


Oui, l'inversion de dépendance demande un peu plus d'effort pour écrire au lieu de coder en dur la dépendance. Oui, c'est embêtant d'avoir à taper plus.

Mais c'est le même argument que de choisir de coder en dur des valeurs littérales car "la déclaration de variables nécessite plus d'effort". Techniquement correct, mais les avantages l'emportent sur les inconvénients de plusieurs ordres de grandeur .

L'avantage de l'inversion de dépendance n'est pas ressenti lorsque vous créez la première version de l'application. Les avantages de l'inversion de dépendance se manifestent lorsque vous devez modifier ou étendre cette version initiale. Et ne vous trompez pas en pensant que vous vous y prendrez bien la première fois et que vous n'aurez pas besoin d'étendre / modifier le code. Vous devrez changer les choses.


cela affecte-t-il la performance dans son ensemble?

Cela n'affecte pas les performances d'exécution de l'application. Mais cela influe énormément sur le temps de développement (et donc les performances) du développeur .

Flater
la source
2
Si vous supprimiez votre commentaire, " En tant que commentaire mineur, votre question porte sur l'inversion de dépendance, et non sur l'injection de dépendance. L'injection est un moyen de procéder à l'inversion, mais ce n'est pas le seul moyen. ", Ce serait une excellente réponse. L'inversion de dépendance peut être obtenue via une injection ou un localisateur / globals. Les exemples de la question concernent l'injection. La question est sur l' injection de dépendance (ainsi que la dépendance inversion).
David Arno
12
Je pense que toute la question de la voiture est un peu abêtue et déroutante
Ewan
4
@Flater, une partie du problème est que personne ne semble vraiment s'accorder sur la différence entre l'injection de dépendance, l'inversion de dépendance et l'inversion de contrôle. Une chose est sûre cependant, un "conteneur" n'est certainement pas nécessaire pour injecter une dépendance dans une méthode ou un constructeur. Le DI pur (ou le pauvre) décrit spécifiquement l’injection manuelle de dépendance. C'est la seule injection de dépendance que j'utilise personnellement car je n'aime pas la "magie" associée aux contenants.
David Arno
1
@Flater Le problème avec cet exemple est que vous ne modéleriez certainement aucun problème de code de cette façon. Il est donc contre nature, forcé et ajoute plus de questions que de réponses. Cela le rend plus susceptible d'induire en erreur et de confondre que de démontrer.
jpmc26
2
@gbjbaanb: À aucun moment, ma réponse n'a même pénétré à distance dans le polymorphisme. Il n'y a pas d'héritage, d'implémentation d'interface, ou quoi que ce soit qui ressemble à la conversion ascendante / descendante de types. Vous avez complètement mal interprété la réponse.
Flater
0

Comme pour tous les modèles, il est très utile de demander "pourquoi" pour éviter les dessins gonflés.

Pour l'injection de dépendance, cela se voit facilement en pensant aux deux facettes, sans doute les plus importantes, de la conception POO ...

Faible couplage

Couplage en programmation informatique :

En génie logiciel, le couplage est le degré d'interdépendance entre les modules logiciels; une mesure de la proximité des deux routines ou modules; la force des relations entre les modules.

Vous voulez obtenir un faible couplage. Deux choses étant fortement couplées, cela signifie que si vous modifiez l'une, vous devez très probablement modifier l'autre. Des bogues ou des restrictions dans l’un risquent de provoquer des bogues ou des restrictions dans l’autre; etc.

Une classe instanciant des objets aux autres est un couplage très fort, car l’une a besoin de connaître l’existence de l’autre; il doit savoir comment l'instancier (quels arguments le constructeur a besoin), et ces arguments doivent être disponibles lors de l'appel du constructeur. En outre, selon que le langage nécessite une déconstruction explicite (C ++), cela entraînera d'autres complications. Si vous introduisez de nouvelles classes (par exemple, a NextSettingsou autre), vous devez revenir à la classe d'origine et ajouter plus d'appels à leurs constructeurs.

Haute cohésion

Cohésion :

En programmation informatique, la cohésion fait référence au degré d'appartenance des éléments d'un module.

C'est le revers de la médaille. Si vous examinez une unité de code (une méthode, une classe, un package, etc.), vous souhaitez que tout le code à l'intérieur de cette unité ait le moins de responsabilités possible.

Un exemple de base pour cela serait le modèle MVC: vous séparez clairement le modèle de domaine de la vue (GUI) et un calque de contrôle qui les colle ensemble.

Cela évite le gonflement du code où vous obtenez de gros morceaux qui font beaucoup de choses différentes; si vous souhaitez en modifier une partie, vous devez également suivre toutes les autres fonctionnalités, en essayant d'éviter les bugs, etc. et vous vous programmez rapidement dans un trou où il est très difficile de sortir.

Avec l'injection de dépendance, vous déléguez la création ou le suivi des dépendances à toutes les classes (ou fichiers de configuration) qui implémentent votre ID. D'autres classes ne se soucieront pas beaucoup de ce qui se passe exactement - elles travailleront avec des interfaces génériques et n'auront aucune idée de la mise en œuvre réelle, ce qui signifie qu'elles ne sont pas responsables des autres tâches.

AnoE
la source
-1

Vous devez utiliser des techniques pour résoudre les problèmes qu’ils maîtrisent bien lorsque vous avez ces problèmes. L'inversion de dépendance et l'injection ne sont pas différentes.

L'inversion de dépendance ou l'injection est une technique qui permet à votre code de décider de quelle implémentation d'une méthode est appelée au moment de l'exécution. Cela maximise les avantages d'une liaison tardive. Cette technique est nécessaire lorsque le langage ne prend pas en charge le remplacement à l'exécution de fonctions autres que l'instance. Par exemple, Java ne dispose pas d'un mécanisme permettant de remplacer les appels d'une méthode statique par des appels d'une autre implémentation; contraste avec Python, où tout ce qui est nécessaire pour remplacer la fonction est de lier le nom à une autre fonction (réaffecter la variable contenant la fonction).

Pourquoi voudrions-nous faire varier la mise en œuvre de la fonction? Il y a deux raisons principales:

  • Nous voulons utiliser des faux pour des tests. Cela nous permet de tester une classe qui dépend d'une extraction de base de données sans se connecter réellement à la base de données.
  • Nous devons prendre en charge plusieurs implémentations. Par exemple, il peut être nécessaire de configurer un système prenant en charge les bases de données MySQL et PostgreSQL.

Vous pouvez également prendre note de l'inversion des conteneurs de contrôle. C'est une technique destinée à vous aider à éviter les arbres de construction énormes et enchevêtrés qui ressemblent à ce pseudocode:

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

Il vous permet d’inscrire vos cours et d’effectuer ensuite la construction pour vous:

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

Notez que c'est plus simple si les classes enregistrées peuvent être des singletons sans état .

Mot d'avertissement

Notez que l'inversion de dépendance ne devrait pas être votre solution idéale pour la logique de découplage. Recherchez les possibilités d'utiliser le paramétrage à la place. Considérons cette méthode de pseudocode par exemple:

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

Nous pourrions utiliser l'inversion de dépendance pour certaines parties de cette méthode:

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

Mais nous ne devrions pas, du moins pas complètement. Notez que nous avons créé une stateful classe Querier. Il contient maintenant une référence à un objet de connexion essentiellement global. Cela crée des problèmes tels que la difficulté à comprendre l'état général du programme et la coordination des différentes classes. Notez également que nous sommes obligés de simuler le demandeur ou la connexion si nous voulons tester la logique de calcul de la moyenne. En outre, une meilleure approche consisterait à augmenter le paramétrage :

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

Et la connexion serait gérée à un niveau encore plus élevé, responsable de l'opération dans son ensemble et sachant quoi faire avec cette sortie.

Nous pouvons maintenant tester la logique de calcul de la moyenne de manière totalement indépendante de l'interrogation, et nous pouvons en outre l'utiliser dans une plus grande variété de situations. Nous pouvons nous demander si nous avons même besoin des objets MyQuerieret Averager, et la réponse est peut-être que nous n'en avons pas si nous n'avons pas l'intention de procéder à des tests unitaires StuffDoer, et que les tests unitaires StuffDoerseraient parfaitement raisonnables, car ils sont si étroitement liés à la base de données. Il serait peut-être plus judicieux de laisser les tests d'intégration le couvrir. Dans ce cas, nous pourrions être bien prise fetchAboveMinet averageDatadans les méthodes statiques.

jpmc26
la source
2
"L' injection de dépendance est une technique destinée à vous aider à éviter les arbres de construction énormes et enchevêtrés ... ". Votre premier exemple après cette affirmation est un exemple d’injection de dépendance pure ou pour homme pauvre. Le second est un exemple d'utilisation d'un conteneur IoC pour "automatiser" l'injection de ces dépendances. Les deux sont des exemples d'injection de dépendance en action.
David Arno
@ DavidArno Ouais, vous avez raison. J'ai ajusté la terminologie.
jpmc26
Il existe une troisième raison principale de modifier l’implémentation, ou du moins de concevoir le code en supposant que l’implémentation peut varier: cela incite le développeur à avoir un couplage faible et à éviter d’écrire du code difficile à modifier si une nouvelle implémentation était implémentée à un moment donné. l'avenir. Et bien que cela puisse ne pas être une priorité dans certains projets (par exemple, sachant que l'application ne sera jamais réexaminée après sa publication initiale), ce le sera dans d'autres (par exemple, lorsque le modèle d'entreprise de la société essaie spécifiquement de proposer un support / extension étendu de leurs applications. ).
Flater
L’injection @Flater Dependency entraîne toujours un couplage fort. Cela lie la logique à une interface particulière et oblige le code en question à savoir ce que cette interface fait. Considérons le code qui transforme les résultats extraits d'une base de données. Si j'utilise DI pour les séparer, le code doit toujours savoir qu'un téléchargement de base de données a lieu et l'invoquer. La meilleure solution est si le code de transformation ne sait même pas qu’une extraction a lieu . La seule façon de le faire est si les résultats de l'extraction sont transmis par un appelant, plutôt que par l'injection du récupérateur.
jpmc26
1
@ jpmc26 Mais dans votre exemple (commentaire), la base de données n'avait même pas besoin d'être une dépendance pour commencer. Bien sûr, éviter les dépendances est préférable pour un couplage lâche, mais DI se concentre sur la mise en œuvre des dépendances nécessaires.
Flater