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;
}
}
deactivateProfile
me suggère que définir la valeurisActive
false 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).Réponses:
L’avantage est que, sans injection de dépendance, votre classe de profil
Mais avec l'injection de dépendance
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.
la source
This may seem (or even be) irrelevant in this particular case
Je 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.$setting = new Setting();
terriblement inefficace. L'injection et l'instanciation d'objet se produisent une fois.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:
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
la source
Pourquoi (quel est même le problème)?
Le meilleur mnémonique que j'ai trouvé pour cela est " new is glue ": chaque fois que vous utilisez
new
dans 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 unRaceTrack
, qui en crée 8Cars
, qui créent chacune unMotor
. Maintenant, si vous voulez 4 supplémentairesCars
avec une accélération différente, vous devrez changer chaque classe mentionnée , sauf peut-êtreGame
.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:
L'ajout de ces 4 voitures différentes peut maintenant être fait en ajoutant ces lignes:
RaceTrack
,Game
,Car
, ouMotor
étaient nécessaires - ce qui signifie que nous pouvons être sûrs à 100% que nous n'introduisons de nouveaux insectes là - bas!Considérations de performance
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
Car
ou unMotor
doit maintenant lire un fichier. Cela ne semble pas trop logique.Dans la version DI, vous l'ajouteriez au code configurant le jeu.
la source
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
Setting
type / valeur en particulier? Existe-t-il une bonne raison pour que le code d’appel demande un autreSetting
type / valeur? (Rappelez-vous, les tests sont du code !)la source
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:
Ensuite, je dirais que vous utilisez l'injection de dépendance. La différence notable est qu'un
Settings
objet est transmis au constructeur ou à unProfile
objet.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:
Instanciez l'objet Settings dans le constructeur de profils
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.la source
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.Je sais que je viens en retard à cette fête, mais je sens qu’un point important nous manque.
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:
quand nous obtenons à peu près la même chose:
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:
Et maintenant, le
application
est libre d'activer et de désactiver leprofile
sans rien savoir en particulier sur le faitsetting
que cela change réellement. Pourquoi est-ce bien? Au cas où vous auriez besoin de changer les paramètres. Le systèmeapplication
est 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.
la source
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:
Il y a des avantages énormes à ne pas avoir votre mécanicien créer tout à partir de zéro:
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 (
Settings
dans 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’Settings
objets.De plus, quiconque consommera votre classe devra également demander à la classe de créer le bon
Settings
objet, au lieu de pouvoir simplement transmettre à la classe toutSettings
objet 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 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 .
la source
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 :
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
NextSettings
ou autre), vous devez revenir à la classe d'origine et ajouter plus d'appels à leurs constructeurs.Haute cohésion
Cohésion :
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.
la source
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:
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:
Il vous permet d’inscrire vos cours et d’effectuer ensuite la construction pour vous:
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:
Nous pourrions utiliser l'inversion de dépendance pour certaines parties de cette méthode:
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 :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
MyQuerier
etAverager
, 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 unitairesStuffDoer
, et que les tests unitairesStuffDoer
seraient 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 prisefetchAboveMin
etaverageData
dans les méthodes statiques.la source