Quel est le principe d'inversion de dépendance et pourquoi est-il important?
oop
solid-principles
glossary
principles
dependency-inversion
Phillip Wells
la source
la source
Réponses:
Consultez ce document: Le principe d'inversion de dépendance .
Il dit essentiellement:
Quant à savoir pourquoi c'est important, en bref: les changements sont risqués, et en dépendant d'un concept plutôt que d'une implémentation, vous réduisez le besoin de changement sur les sites d'appels.
En effet, le DIP réduit le couplage entre différents morceaux de code. L'idée est que bien qu'il existe de nombreuses façons d'implémenter, par exemple, une fonction de journalisation, la façon dont vous l'utiliseriez devrait être relativement stable dans le temps. Si vous pouvez extraire une interface qui représente le concept de journalisation, cette interface devrait être beaucoup plus stable dans le temps que son implémentation, et les sites d'appels devraient être beaucoup moins affectés par les modifications que vous pourriez apporter tout en maintenant ou en étendant ce mécanisme de journalisation.
En faisant également dépendre l'implémentation d'une interface, vous avez la possibilité de choisir au moment de l'exécution quelle implémentation est la mieux adaptée à votre environnement particulier. Selon les cas, cela peut également être intéressant.
la source
Les livres Développement logiciel agile, principes, modèles et pratiques et Principes, modèles et pratiques Agile en C # sont les meilleures ressources pour comprendre pleinement les objectifs et les motivations d'origine du principe d'inversion de dépendance. L'article "The Dependency Inversion Principle" est également une bonne ressource, mais étant donné qu'il s'agit d'une version condensée d'un projet qui a finalement fait son chemin dans les livres mentionnés précédemment, il laisse de côté une discussion importante sur le concept d'un la propriété du package et de l'interface qui sont essentielles pour distinguer ce principe du conseil plus général de «programmer vers une interface, pas une implémentation» qui se trouve dans le livre Design Patterns (Gamma, et. al).
Pour résumer, le principe d'inversion de dépendance consiste principalement à inverser la direction conventionnelle des dépendances des composants de «niveau supérieur» vers les composants de «niveau inférieur» de telle sorte que les composants de «niveau inférieur» dépendent des interfaces appartenant aux composants de «niveau supérieur» . (Remarque: le composant de "niveau supérieur" se réfère ici au composant nécessitant des dépendances / services externes, pas nécessairement sa position conceptuelle dans une architecture en couches.) Ce faisant, le couplage n'est pas tellement réduit qu'il est décalé des composants qui sont théoriquement moins précieux pour les composants qui sont théoriquement plus précieux.
Ceci est réalisé en concevant des composants dont les dépendances externes sont exprimées en termes d'interface pour laquelle une implémentation doit être fournie par le consommateur du composant. En d'autres termes, les interfaces définies expriment ce dont le composant a besoin, et non la façon dont vous utilisez le composant (par exemple "INeedSomething", pas "IDoSomething").
Ce à quoi le principe d'inversion de dépendance ne fait pas référence est la simple pratique d'abstraction des dépendances par l'utilisation d'interfaces (par exemple MyService → [ILogger ⇐ Logger]). Bien que cela dissocie un composant du détail d'implémentation spécifique de la dépendance, cela n'inverse pas la relation entre le consommateur et la dépendance (par exemple [MyService → IMyServiceLogger] ⇐ Logger.
L'importance du principe d'inversion de dépendance peut être résumée à un seul objectif de pouvoir réutiliser des composants logiciels qui reposent sur des dépendances externes pour une partie de leurs fonctionnalités (journalisation, validation, etc.)
Dans le cadre de cet objectif général de réutilisation, nous pouvons délimiter deux sous-types de réutilisation:
Utilisation d'un composant logiciel dans plusieurs applications avec des implémentations de sous-dépendances (par exemple, vous avez développé un conteneur DI et souhaitez fournir une journalisation, mais ne souhaitez pas coupler votre conteneur à un enregistreur spécifique de sorte que tous ceux qui utilisent votre conteneur doivent également utilisez la bibliothèque de journalisation choisie).
Utilisation de composants logiciels dans un contexte en évolution (par exemple, vous avez développé des composants de logique métier qui restent les mêmes dans plusieurs versions d'une application où les détails de mise en œuvre évoluent).
Avec le premier cas de réutilisation de composants sur plusieurs applications, comme avec une bibliothèque d'infrastructure, l'objectif est de fournir un besoin d'infrastructure de base à vos consommateurs sans coupler vos consommateurs aux sous-dépendances de votre propre bibliothèque, car prendre des dépendances sur de telles dépendances nécessite votre les consommateurs doivent également exiger les mêmes dépendances. Cela peut être problématique lorsque les utilisateurs de votre bibliothèque choisissent d'utiliser une bibliothèque différente pour les mêmes besoins d'infrastructure (par exemple NLog vs log4net), ou s'ils choisissent d'utiliser une version ultérieure de la bibliothèque requise qui n'est pas rétrocompatible avec la version requis par votre bibliothèque.
Avec le deuxième cas de réutilisation de composants de logique métier (c'est-à-dire «composants de niveau supérieur»), l'objectif est d'isoler l'implémentation du domaine principal de votre application des besoins changeants de vos détails d'implémentation (c'est-à-dire modifier / mettre à niveau les bibliothèques de persistance, les bibliothèques de messagerie , stratégies de chiffrement, etc.). Idéalement, la modification des détails d'implémentation d'une application ne devrait pas casser les composants encapsulant la logique métier de l'application.
Remarque: Certains peuvent s'opposer à la description de ce deuxième cas comme une réutilisation réelle, estimant que les composants tels que les composants de logique métier utilisés dans une seule application en évolution ne représentent qu'une seule utilisation. L'idée ici, cependant, est que chaque modification apportée aux détails d'implémentation de l'application rend un nouveau contexte et donc un cas d'utilisation différent, bien que les objectifs ultimes puissent être distingués en tant qu'isolement et portabilité.
Bien que suivre le principe d'inversion de dépendance dans ce deuxième cas puisse offrir un certain avantage, il convient de noter que sa valeur appliquée aux langages modernes tels que Java et C # est très réduite, peut-être au point d'être sans importance. Comme indiqué précédemment, le DIP consiste à séparer complètement les détails d'implémentation en packages séparés. Dans le cas d'une application en évolution, cependant, la simple utilisation d'interfaces définies en termes de domaine métier vous évitera de devoir modifier des composants de niveau supérieur en raison de l'évolution des besoins des composants de détail d'implémentation, même si les détails d'implémentation résident finalement dans le même package. . Cette partie du principe reflète des aspects qui étaient pertinents pour le langage en vue lorsque le principe a été codifié (c'est-à-dire C ++) qui ne sont pas pertinents pour les nouveaux langages. Cela dit,
Une discussion plus longue de ce principe en ce qui concerne l'utilisation simple des interfaces, l'injection de dépendances et le modèle d'interface séparée peut être trouvée ici . De plus, une discussion sur la façon dont le principe se rapporte aux langages à typage dynamique tels que JavaScript peut être trouvée ici .
la source
Lorsque nous concevons des applications logicielles, nous pouvons considérer les classes de bas niveau les classes qui implémentent les opérations de base et primaires (accès disque, protocoles réseau, ...) et les classes de haut niveau les classes qui encapsulent des logiques complexes (flux métier, ...).
Les derniers reposent sur les classes de bas niveau. Une manière naturelle d'implémenter de telles structures serait d'écrire des classes de bas niveau et une fois que nous les avons, d'écrire les classes complexes de haut niveau. Puisque les classes de haut niveau sont définies par rapport aux autres, cela semble la manière logique de le faire. Mais ce n'est pas une conception flexible. Que se passe-t-il si nous devons remplacer une classe de bas niveau?
Le principe d'inversion de dépendance stipule que:
Ce principe cherche à «inverser» la notion conventionnelle selon laquelle les modules de haut niveau dans le logiciel devraient dépendre des modules de niveau inférieur. Ici, les modules de haut niveau possèdent l'abstraction (par exemple, décider des méthodes de l'interface) qui sont implémentées par des modules de niveau inférieur. Rendant ainsi les modules de niveau inférieur dépendants des modules de niveau supérieur.
la source
L'inversion de dépendances bien appliquée donne flexibilité et stabilité au niveau de toute l'architecture de votre application. Cela permettra à votre application d'évoluer de manière plus sécurisée et stable.
Architecture traditionnelle en couches
Traditionnellement, une interface utilisateur à architecture en couches dépendait de la couche métier et cela dépendait à son tour de la couche d'accès aux données.
Vous devez comprendre la couche, le package ou la bibliothèque. Voyons comment serait le code.
Nous aurions une bibliothèque ou un package pour la couche d'accès aux données.
Et une autre logique métier de couche de bibliothèque ou de package qui dépend de la couche d'accès aux données.
Architecture en couches avec inversion des dépendances
L'inversion de dépendance indique ce qui suit:
Quels sont les modules de haut niveau et de bas niveau? En pensant aux modules tels que les bibliothèques ou les packages, les modules de haut niveau seraient ceux qui ont traditionnellement des dépendances et de bas niveau dont ils dépendent.
En d'autres termes, le niveau haut du module serait l'endroit où l'action est invoquée et le niveau bas où l'action est exécutée.
Une conclusion raisonnable à tirer de ce principe est qu'il ne doit pas y avoir de dépendance entre les concrétions, mais qu'il doit y avoir une dépendance à une abstraction. Mais selon l'approche que nous adoptons, nous pouvons mal appliquer la dépendance à l'investissement, mais une abstraction.
Imaginez que nous adaptons notre code comme suit:
Nous aurions une bibliothèque ou un package pour la couche d'accès aux données qui définissent l'abstraction.
Et une autre logique métier de couche de bibliothèque ou de package qui dépend de la couche d'accès aux données.
Bien que nous dépendions d'une abstraction, la dépendance entre les entreprises et l'accès aux données reste la même.
Pour obtenir une inversion de dépendance, l'interface de persistance doit être définie dans le module ou le package où se trouve cette logique ou ce domaine de haut niveau et non dans le module de bas niveau.
Définissez d'abord ce qu'est la couche de domaine et l'abstraction de sa communication est définie comme la persistance.
Une fois que la couche de persistance dépend du domaine, il faut s'inverser maintenant si une dépendance est définie.
(source: xurxodev.com )
Approfondir le principe
Il est important de bien assimiler le concept, d'approfondir le but et les avantages. Si nous restons mécaniquement et apprenons le référentiel de cas typique, nous ne serons pas en mesure d'identifier où nous pouvons appliquer le principe de dépendance.
Mais pourquoi inversons-nous une dépendance? Quel est l'objectif principal au-delà des exemples spécifiques?
Cela permet généralement aux choses les plus stables, qui ne dépendent pas de choses moins stables, de changer plus fréquemment.
Il est plus facile pour le type de persistance d'être changé, que ce soit la base de données ou la technologie pour accéder à la même base de données que la logique de domaine ou les actions conçues pour communiquer avec persistance. Pour cette raison, la dépendance est inversée car il est plus facile de modifier la persistance si ce changement se produit. De cette façon, nous n'aurons pas à changer de domaine. La couche de domaine est la plus stable de toutes, c'est pourquoi elle ne devrait dépendre de rien.
Mais il n'y a pas que cet exemple de référentiel. Il existe de nombreux scénarios où ce principe s'applique et il existe des architectures basées sur ce principe.
Architectures
Il existe des architectures où l'inversion des dépendances est la clé de sa définition. Dans tous les domaines, c'est le plus important et ce sont les abstractions qui indiqueront que le protocole de communication entre le domaine et le reste des packages ou bibliothèques sont définis.
Architecture propre
Dans l' architecture propre, le domaine est situé au centre et si vous regardez dans le sens des flèches indiquant la dépendance, il est clair quelles sont les couches les plus importantes et les plus stables. Les couches extérieures sont considérées comme des outils instables, alors évitez de dépendre d'eux.
(source: 8thlight.com )
Architecture hexagonale
Il en va de même avec l'architecture hexagonale, où le domaine est également situé dans la partie centrale et les ports sont des abstractions de communication depuis le domino vers l'extérieur. Là encore, il est évident que le domaine est le plus stable et la dépendance traditionnelle est inversée.
la source
Pour moi, le principe d'inversion de dépendance, tel que décrit dans l' article officiel , est en réalité une tentative malavisée d'augmenter la réutilisabilité des modules qui sont intrinsèquement moins réutilisables, ainsi qu'un moyen de contourner un problème dans le langage C ++.
Le problème en C ++ est que les fichiers d'en-tête contiennent généralement des déclarations de champs et de méthodes privés. Par conséquent, si un module C ++ de haut niveau inclut le fichier d'en-tête pour un module de bas niveau, cela dépendra des détails d' implémentation réels de ce module. Et ce n'est évidemment pas une bonne chose. Mais ce n'est pas un problème dans les langues plus modernes couramment utilisées aujourd'hui.
Les modules de haut niveau sont intrinsèquement moins réutilisables que les modules de bas niveau, car les premiers sont normalement plus spécifiques à l'application / au contexte que les seconds. Par exemple, un composant qui implémente un écran UI est du plus haut niveau et aussi très (complètement?) Spécifique à l'application. Essayer de réutiliser un tel composant dans une application différente est contre-productif et ne peut conduire qu'à une sur-ingénierie.
Ainsi, la création d'une abstraction séparée au même niveau d'un composant A qui dépend d'un composant B (qui ne dépend pas de A) ne peut se faire que si le composant A sera vraiment utile pour une réutilisation dans différentes applications ou contextes. Si ce n'est pas le cas, appliquer DIP serait une mauvaise conception.
la source
En gros, il dit:
La classe doit dépendre d'abstractions (par exemple, interface, classes abstraites) et non de détails spécifiques (implémentations).
la source
De bonnes réponses et de bons exemples sont déjà donnés par d'autres ici.
La raison pour laquelle le DIP est important est qu'il garantit le principe OO "conception à couplage lâche".
Les objets de votre logiciel ne doivent PAS entrer dans une hiérarchie où certains objets sont ceux de premier niveau, dépendant des objets de bas niveau. Les modifications des objets de bas niveau se répercuteront ensuite sur vos objets de niveau supérieur, ce qui rend le logiciel très fragile pour le changement.
Vous voulez que vos objets «de premier niveau» soient très stables et non fragiles pour le changement, vous devez donc inverser les dépendances.
la source
Une manière beaucoup plus claire d'énoncer le principe d'inversion de dépendance est:
Vos modules qui encapsulent une logique métier complexe ne doivent pas dépendre directement d'autres modules qui encapsulent une logique métier. Au lieu de cela, ils ne devraient dépendre que des interfaces vers des données simples.
C'est-à-dire, au lieu d'implémenter votre classe
Logic
comme le font habituellement les gens:vous devriez faire quelque chose comme:
Data
etDataFromDependency
devrait vivre dans le même module queLogic
, pas avecDependency
.Pourquoi faire ceci?
Dependency
vous changez, vous n'avez pas besoin de changerLogic
.Logic
fait est une tâche beaucoup plus simple: cela ne fonctionne que sur ce qui ressemble à un ADT.Logic
peuvent désormais être testés plus facilement. Vous pouvez désormais instancier directementData
avec de fausses données et les transmettre. Pas besoin de simulacres ou de bancs d'essai complexes.la source
DataFromDependency
, qui référence directementDependency
, se trouve dans le même module queLogic
, alors leLogic
module dépend toujours directement duDependency
module au moment de la compilation. Selon l'explication de l'oncle Bob du principe , éviter cela est tout l'intérêt de DIP. Plutôt, pour suivre DIP,Data
devrait être dans le même module queLogic
, maisDataFromDependency
devrait être dans le même module queDependency
.L'inversion de contrôle (IoC) est un modèle de conception dans lequel un objet reçoit sa dépendance par un cadre extérieur, plutôt que de demander à un cadre sa dépendance.
Exemple de pseudocode utilisant la recherche traditionnelle:
Code similaire utilisant IoC:
Les avantages d'IoC sont:
la source
Le point d'inversion des dépendances est de créer des logiciels réutilisables.
L'idée est qu'au lieu de deux morceaux de code reposant l'un sur l'autre, ils s'appuient sur une interface abstraite. Ensuite, vous pouvez réutiliser l'une ou l'autre pièce sans l'autre.
Le moyen le plus courant consiste à utiliser un conteneur d'inversion de contrôle (IoC) comme Spring en Java. Dans ce modèle, les propriétés des objets sont configurées via une configuration XML au lieu des objets sortant et trouvant leur dépendance.
Imaginez ce pseudo-code ...
MyClass dépend directement à la fois de la classe Service et de la classe ServiceLocator. Il a besoin de ces deux éléments si vous souhaitez l'utiliser dans une autre application. Maintenant, imaginez ceci ...
Désormais, MyClass repose sur une seule interface, l'interface IService. Nous laisserions le conteneur IoC définir la valeur de cette variable.
Alors maintenant, MyClass peut facilement être réutilisé dans d'autres projets, sans apporter la dépendance de ces deux autres classes avec elle.
Mieux encore, vous n'avez pas à faire glisser les dépendances de MyService, et les dépendances de ces dépendances, et le ... eh bien, vous voyez l'idée.
la source
Si nous pouvons prendre pour acquis qu’un employé «de haut niveau» dans une entreprise est payé pour l’exécution de ses plans, et que ces plans sont fournis par l’exécution globale de nombreux plans d’employés de «bas niveau», alors nous pourrions dire c'est généralement un plan terrible si la description du plan de l'employé de haut niveau est couplée de quelque façon que ce soit au plan spécifique de tout employé de niveau inférieur.
Si un cadre de haut niveau a un plan pour «améliorer le délai de livraison» et indique qu'un employé de la ligne maritime doit prendre un café et faire des étirements chaque matin, alors ce plan est fortement couplé et a une faible cohésion. Mais si le plan ne mentionne aucun employé en particulier, et exige en fait simplement "qu'une entité qui peut effectuer le travail est prête à travailler", alors le plan est faiblement couplé et plus cohérent: les plans ne se chevauchent pas et peuvent facilement être remplacés . Les sous-traitants, ou robots, peuvent facilement remplacer les employés et le plan du haut niveau reste inchangé.
«Niveau élevé» dans le principe d'inversion de dépendance signifie «plus important».
la source
Je peux voir qu'une bonne explication a été donnée dans les réponses ci-dessus. Cependant, je veux fournir une explication simple avec un exemple simple.
Le principe d'inversion de dépendance permet au programmeur de supprimer les dépendances codées en dur afin que l'application devienne faiblement couplée et extensible.
Comment y parvenir: par l'abstraction
Sans inversion de dépendance:
Dans l'extrait de code ci-dessus, l'objet d'adresse est codé en dur. Au lieu de cela, si nous pouvons utiliser l'inversion de dépendance et injecter l'objet d'adresse en passant par le constructeur ou la méthode setter. Voyons voir.
Avec l'inversion de dépendance:
la source
Inversion de dépendance: dépend des abstractions, pas des concrétions.
Inversion de contrôle: Main vs Abstraction, et comment le Main est le ciment des systèmes.
Voici quelques bons articles qui en parlent:
https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/
https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/
https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/
la source
Disons que nous avons deux classes:
Engineer
etProgrammer
:L'ingénieur de classe a une dépendance sur la classe de programmeur, comme ci-dessous:
Dans cet exemple, la classe
Engineer
a une dépendance sur notreProgrammer
classe. Que se passera-t-il si je dois changer leProgrammer
?De toute évidence, je dois changer le
Engineer
aussi. (Wow, à ce stadeOCP
est violé aussi)Alors, que faut-il pour nettoyer ce gâchis? La réponse est en fait l'abstraction. Par abstraction, nous pouvons supprimer la dépendance entre ces deux classes. Par exemple, je peux créer un
Interface
pour la classe Programmer et à partir de maintenant chaque classe qui veut utiliser leProgrammer
doit l'utiliserInterface
, Ensuite, en changeant la classe Programmer, nous n'avons pas besoin de changer les classes qui l'utilisaient, En raison de l'abstraction que nous utilisé.Remarque:
DependencyInjection
peut nous aider à faireDIP
etSRP
aussi.la source
Pour ajouter à la rafale de réponses généralement bonnes, j'aimerais ajouter un petit échantillon de mon choix pour démontrer une bonne ou une mauvaise pratique. Et oui, je ne suis pas du genre à jeter des pierres!
Disons que vous voulez un petit programme pour convertir une chaîne au format base64 via les E / S de la console. Voici l'approche naïve:
Le DIP dit fondamentalement que les composants de haut niveau ne devraient pas dépendre d'une implémentation de bas niveau, où «niveau» est la distance des E / S selon Robert C. Martin («Clean Architecture»). Mais comment sortir de cette situation difficile? Simplement en rendant l'encodeur central dépendant uniquement des interfaces sans se soucier de leur implémentation:
Notez que vous n'avez pas besoin de toucher
GoodEncoder
pour changer le mode d'E / S - cette classe est satisfaite des interfaces d'E / S qu'elle connaît; toute implémentation de bas niveauIReadable
etIWriteable
ne la dérangera jamais.la source
GoodEncoder
votre deuxième exemple. Pour créer un exemple DIP, vous devez introduire une notion de ce qui "possède" les interfaces que vous avez extraites ici - et, en particulier, les mettre dans le même package que GoodEncoder pendant que leurs implémentations restent à l'extérieur.Le principe d'inversion de dépendance (DIP) dit que
i) Les modules de haut niveau ne devraient pas dépendre de modules de bas niveau. Les deux devraient dépendre d'abstractions.
ii) Les abstractions ne devraient jamais dépendre de détails. Les détails doivent dépendre d'abstractions.
Exemple:
Remarque: la classe doit dépendre d'abstractions comme l'interface ou les classes abstraites, pas de détails spécifiques (implémentation de l'interface).
la source