web-dev-qa-db-fra.com

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

J'ai du mal à chercher des ressources sur les raisons pour lesquelles je devrais utiliser injection de dépendance . La plupart des ressources que je vois explique qu'il passe juste une instance d'un objet à une autre instance d'un objet, mais pourquoi? Est-ce juste pour une architecture/un code plus propre ou cela affecte-t-il les performances dans leur 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;
    }
}
99
Daniel

L'avantage est que sans injection de dépendance, votre classe Profile

  • a besoin de savoir comment créer un objet Paramètres (viole 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 de création d'objets Settings est ailleurs
  • Il est facile d'utiliser différents types d'objets Paramètres

Cela peut sembler (ou même être) hors de propos dans ce cas particulier, mais imaginez si nous ne parlons pas d'un objet Settings, mais d'un objet DataStore, qui pourrait 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 voulez également une implémentation simulée. Maintenant, vous ne voulez vraiment pas que la classe Profile code en dur celle qu'elle utilise - et plus important encore, vous vraiment, vraiment vous ne voulez pas que la classe Profile connaisse les chemins du système de fichiers, les connexions DB et les mots de passe , donc la création d'objets DataStore has doit se produire ailleurs.

104
Michael Borgwardt

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

J'ai appris cela de première main lorsque j'ai été chargé de corriger un bogue difficile à attraper dans l'intégration Paypal de Magento.

Un problème surviendrait lorsque Paypal informait Magento d'un échec de paiement: Magento n'enregistrerait pas l'échec correctement.

Tester un correctif potentiel "manuellement" serait très fastidieux: il vous faudrait en quelque sorte déclencher une notification Paypal "Echec". Vous devez soumettre un chèque électronique, l'annuler et attendre qu'il se trompe. Cela signifie plus de 3 jours pour tester un changement de code à un caractère!

Heureusement, il semble que les développeurs principaux de Magento qui ont développé cette fonction avaient à l'esprit les tests et ont utilisé un modèle d'injection de dépendance pour le rendre trivial. 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 a de nombreux autres avantages, mais une testabilité accrue est le plus grand avantage dans mon esprit .

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

64
Eric Seastrand

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 " nouveau est la colle ": Chaque fois que vous utilisez new dans votre code, ce code est lié à ce spécifique la mise en oeuvre. Si vous utilisez à plusieurs reprises new dans les constructeurs, vous créerez une chaîne d'implémentations spécifiques . Et parce que 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 crée 8 Cars, qui créent chacune un Motor. Maintenant, si vous voulez 4 Cars supplémentaires avec 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/un code plus propre

Oui .

Cependant, cela pourrait très bien sembler moins clair dans cette situation, car il s'agit plutôt d'un exemple de comment le faire . L'avantage réel ne s'affiche que lorsque plusieurs classes sont impliquées et est plus difficile à démontrer, mais imaginez que vous auriez 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 n'était nécessaire - ce qui signifie que nous pouvons être sûrs à 100% que nous n'avons introduit aucun nouveau bogue Là!
  • Au lieu de devoir sauter entre plusieurs fichiers, vous pourrez voir la modification complète sur votre écran en même temps. C'est le résultat de voir la création/installation/configuration comme un propre responsabilité - ce n'est pas le travail d'une voiture de construire un moteur.

Considérations sur les performances

ou cela affecte-t-il les performances dans leur ensemble?

Non . Mais pour être tout à fait honnête avec vous, c'est possible.

Cependant, même dans ce cas, c'est une quantité tellement ridicule que vous n'avez pas besoin de vous en soucier. Si à un moment donné dans le futur, vous devez écrire du code pour un tamagotchi avec l'équivalent d'un processeur de 5 MHz et de 2 Mo de RAM, alors peut-être vous pourriez doivent se soucier de cela.

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

* numéro entièrement composé

Informations ajoutées: "codées en dur"

Ne vous y trompez pas, ceci est toujours très "codé en dur" - les nombres sont écrits directement dans le code. Non codé en dur signifierait quelque chose comme le stockage de ces valeurs dans un fichier texte - par exemple au format JSON - puis en les lisant à partir de ce fichier.

Pour ce faire, vous devez ajouter du code pour lire un fichier, puis analyser JSON. Si vous considérez à nouveau l'exemple; dans la version non DI, un Car ou un Motor doit maintenant lire un fichier. Cela ne semble pas trop logique.

Dans la version DI, vous l'ajouteriez au code de configuration du jeu.

26
R. Schmitz

J'ai toujours été déconcerté par l'injection de dépendance. Il semblait exister uniquement dans les sphères Java Java, mais ces sphères en parlaient avec une grande révérence. C'était l'un des grands modèles, vous voyez, qui sont censés mettre de l'ordre dans le chaos. Mais le les exemples étaient toujours compliqués et artificiels, établissant un non-problème puis cherchant à le résoudre en compliquant le code.

Cela avait plus de sens quand un collègue Python dev m'a transmis cette sagesse: c'est juste passer des arguments aux fonctions. C'est à peine un modèle du tout; plus comme un rappel que vous peut demander quelque chose comme argument, même si vous auriez pu vous-même fournir une valeur raisonnable.

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

Cela a un coût, bien sûr, car maintenant vous forcez l'appelant à faire certains décision (à moins que vous ne rendiez l'argument facultatif), 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 devez spécifiquement utiliser ce type/valeur Setting particulier? Existe-t-il une bonne raison pour laquelle le code appelant peut vouloir un type/valeur Setting différent? (Rappelez-vous, les tests sont du code !)

17
Eevee

Je sais que je viens en retard à cette fête, mais je pense qu'un point important est manqué.

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 cela fait mal.

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

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

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

$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 au lieu de cela nous avions ceci:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

Et maintenant, le application est libre d'activer et de désactiver le profile sans avoir à connaître en particulier le setting qu'il est en train de changer. Pourquoi est-ce bien? Au cas où vous auriez besoin de changer le réglage. Le application est isolé de ces changements, vous êtes donc libre de devenir fou dans un espace confiné sûr sans avoir à regarder tout se casser dès que vous touchez quelque chose.

Cela suit le principe construction distincte du comportement . Le modèle DI ici est simple. Créez tout ce dont vous avez besoin au niveau le plus bas possible, connectez-les ensemble, puis démarrez tout le comportement en cochant un seul appel.

Le résultat est que vous avez un endroit séparé pour décider de ce qui se connecte à 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 n'aide pas.

7
candied_orange

L'exemple que vous donnez n'est pas l'injection de dépendances au sens classique. L'injection de dépendance se réfère généralement au passage d'objets dans un constructeur ou en utilisant une "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 comme argument à une méthode d'instance. Cette méthode d'instance modifie ensuite un champ sur cet objet. Injection de dépendance? Non. Briser l'encapsulation et la dissimulation des données? Absolument!

Maintenant, si le code était comme ceci:

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 objet Settings est passé au constructeur ou un objet Profile.

Ceci est utile si l'objet Settings est coûteux ou complexe à construire, ou si Settings est une interface ou une classe abstraite où plusieurs implémentations concrètes existent afin de changer le comportement d'exécution.

Étant donné que vous accédez directement à un champ sur l'objet Paramètres plutôt que d'appeler une méthode, vous ne pouvez pas profiter 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. Instancier l'objet Paramètres dans le constructeur de profil

  2. Passez l'objet Paramètres dans le constructeur et copiez sur les champs individuels qui s'appliquent au profil

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

7
Greg Burghardt

En tant que client, lorsque vous embauchez un mécanicien pour faire quelque chose à votre voiture, vous attendez-vous à ce que ce mécanicien construise une voiture à partir de rien pour ensuite travailler avec elle? Non, vous donnez au mécanicien la voiture sur laquelle vous voulez qu'il travaille.

En tant que propriétaire de garage, lorsque vous demandez à un mécanicien de faire quelque chose à une voiture, vous attendez-vous à ce que le mécanicien crée ses propres pièces de tournevis/clé/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 propriétaire d'un garage et souhaitez embaucher quelqu'un pour devenir votre mécanicien. Vous leur apprendrez à être mécanicien (= vous écrirez le code).

Ce qui va être plus simple:

  • Apprenez à un mécanicien à fixer un aileron à 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 avec le tournevis nouvellement créé.

Il y a des avantages énormes à ne pas laisser 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 d'en créer un nouveau.
  • De plus, un mécanicien qui a appris à tout créer devra être beaucoup plus un expert et donc 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 désormais fabriqués à partir de carbone au lieu de plastique; vous devrez recycler (= redévelopper) votre mécanicien expert. Mais votre "simple" mécanicien n'aura pas à être recyclé tant que le becquet peut toujours être fixé de la même manière.
  • Avoir un mécanicien qui ne dépend pas d'une voiture qu'il a construite lui-même signifie que vous avez un mécanicien capable de gérer n'importe quelle voiture qu'il peut recevoir. Y compris les voitures qui n'existaient 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 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 perpétuellement être recyclé chaque fois que l'un de ses de nombreuses responsabilités doivent être mises à 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 changements continus chaque fois qu'une nouvelle version de l'objet (Settings dans votre cas ) est créé, et vous devrez développer une logique interne pour que la classe puisse créer différents types d'objets Settings.

De plus, celui qui consomme votre classe devra maintenant demander à la classe de créer le bon objet Settings, par opposition à simplement être en mesure de passer la classe tout objet Settings qu'il souhaite passer. 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 prend un peu plus d'efforts pour écrire au lieu de coder en dur la dépendance. Oui, c'est ennuyeux de devoir taper plus.

Mais c'est le même argument que de choisir de coder en dur les valeurs littérales parce que "déclarer des variables demande plus d'efforts". 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 connu lorsque vous créez la première version de l'application. L'avantage de l'inversion des dépendances se manifeste lorsque vous devez modifier ou étendre cette version initiale. Et ne vous trompez pas en pensant que vous obtiendrez les bons résultats 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 les performances dans leur ensemble?

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

6
Flater

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

Pour l'injection de dépendances, cela est facilement visible en pensant aux deux facettes les plus importantes de la conception OOP ...

Couplage bas

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é entre deux routines ou modules; la force des relations entre les modules.

Vous souhaitez obtenir un faible couplage . Deux choses étant fortement couplées, cela signifie que si vous changez l'une, vous devrez très probablement changer l'autre. Des bogues ou des restrictions dans l'un entraîneront probablement des bogues/restrictions dans l'autre; etc.

Une classe instanciant les objets des autres est un couplage très fort, car l'un a besoin de connaître l'existence de l'autre; il doit savoir comment l'instancier (de 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 introduira d'autres complications. Si vous introduisez de nouvelles classes (c'est-à-dire un NextSettings ou autre), vous devez revenir à la classe d'origine et ajouter plus d'appels à leurs constructeurs.

Haute cohésion

Cohésion :

Dans la programmation informatique, la cohésion se réfère au degré auquel les éléments à l'intérieur d'un module appartiennent ensemble.

C'est l'autre côté de la médaille. Si vous regardez une unité de code (une méthode, une classe, un package, etc.), vous voulez avoir tout le code à l'intérieur de cette unité pour avoir le moins de responsabilités possible.

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

Cela évite le gonflement du code lorsque vous obtenez de gros morceaux qui font beaucoup de choses différentes; si vous souhaitez en changer une partie, vous devez également garder une trace de 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épendances, vous déléguez la création ou le suivi des dépendances à toutes les classes (ou fichiers de configuration) qui implémentent votre DI. Les autres classes ne se soucieront pas beaucoup de ce qui se passe exactement - elles travailleront avec certaines interfaces génériques et n'ont aucune idée de la mise en œuvre réelle, ce qui signifie qu'elles ne sont pas responsables pour les autres trucs.

0
AnoE