Quel est le principe d'inversion de dépendance et pourquoi est-il important?


178

Quel est le principe d'inversion de dépendance et pourquoi est-il important?



3
Quantité ridicule de réponses ici en utilisant les termes "haut niveau" et "bas niveau" de Wikipedia. Ces conditions sont inaccessibles et conduisent de nombreux lecteurs vers cette page. Si vous allez régurgiter wikipedia, veuillez définir ces termes dans vos réponses pour donner du contexte!
8bitjunkie

Réponses:


107

Consultez ce document: Le principe d'inversion de dépendance .

Il dit essentiellement:

  • Les modules de haut niveau ne doivent pas dépendre de modules de bas niveau. Les deux devraient dépendre d'abstractions.
  • Les abstractions ne devraient jamais dépendre des détails. Les détails doivent dépendre d'abstractions.

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.


31
Cette réponse ne dit pas pourquoi DIP est important, ni même ce qu'est DIP. J'ai lu le document officiel DIP, et je pense que c'est vraiment un «principe» médiocre et injustifié, car il repose sur une hypothèse erronée: que les modules de haut niveau sont réutilisables.
Rogério

3
Prenons un graphe de dépendance pour certains objets. Appliquez DIP aux objets. Désormais, tout objet sera indépendant de l'implémentation des autres objets. Les tests unitaires sont désormais simples. Une refactorisation ultérieure pour une réutilisation est possible. Les modifications de conception ont des portées de modification très limitées. Les problèmes de conception ne sont pas en cascade. Voir aussi le modèle AI "Blackboard" pour l'inversion de la dépendance des données. Ensemble, des outils très puissants pour rendre le logiciel compréhensible, maintenable et fiable. Ignorez l'injection de dépendances dans ce contexte. C'est sans rapport.
Tim Williscroft

6
Une classe A utilisant une classe B ne signifie pas que A "dépend" de l'implémentation de B; cela dépend uniquement de l'interface publique de B. L'ajout d'une abstraction distincte dont A et B dépendent maintenant signifie seulement que A n'aura plus de dépendance à la compilation sur l'interface publique de B. L'isolement entre les unités peut être réalisé facilement sans ces abstractions supplémentaires; il existe des outils de simulation spécifiques pour cela, en Java et .NET, qui traitent toutes les situations (méthodes statiques, constructeurs, etc.). L'application de DIP a tendance à rendre le logiciel plus complexe et moins maintenable, et pas plus testable.
Rogério

1
Alors qu'est-ce que l'inversion de contrôle? un moyen d'atteindre l'inversion de dépendance?
user20358

2
@Rogerio, voir la réponse de Derek Greer ci-dessous, il l'explique. AFAIK, DIP dit que c'est A qui impose ce dont A a besoin, et non B qui dit ce dont A a besoin. Ainsi, l'interface dont A a besoin ne doit pas être donnée par B, mais pour A.
ejaenv

145

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:

  1. 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).

  2. 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 .


17
Merci. Je vois maintenant comment ma réponse passe à côté de l'essentiel. La différence entre MyService → [ILogger ⇐ Logger] et [MyService → IMyServiceLogger] ⇐ Logger est subtile mais importante.
Patrick McElhaney

2
Dans la même ligne, c'est très bien expliqué ici: lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
Comment j'aurais souhaité que les gens arrêtent d'utiliser les enregistreurs comme cas d'utilisation canonique pour l'injection de dépendances. Surtout dans le cadre de log4net où c'est presque un motif anti. Cela mis à part, explication géniale!
Casper Leon Nielsen

4
@ Casper Leon Nielsen - DIP n'a rien à voir avec DI Ce ne sont pas des synonymes ni des concepts équivalents.
TSmith

4
@ VF1 Comme indiqué dans le paragraphe de résumé, l'importance du principe d'inversion de dépendance réside principalement dans la réutilisation. Si John publie une bibliothèque qui a une dépendance externe sur une bibliothèque de journalisation et que Sam souhaite utiliser la bibliothèque de John, Sam prend la dépendance de journalisation transitoire. Sam ne pourra jamais déployer son application sans la bibliothèque de journalisation John sélectionnée. Si John suit le DIP, cependant, Sam est libre de fournir un adaptateur et d'utiliser la bibliothèque de journalisation de son choix. Le DIP n'est pas une question de commodité, mais de couplage.
Derek Greer

14

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:

  • Les modules de haut niveau ne doivent pas dépendre de modules de bas niveau. Les deux devraient dépendre d'abstractions.
  • Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre d'abstractions.

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.


11

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.

// DataAccessLayer.dll
public class ProductDAO {

}

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.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Architecture en couches avec inversion des dépendances

L'inversion de dépendance indique ce qui suit:

Les modules de haut niveau ne doivent pas dépendre de modules de bas niveau. Les deux devraient dépendre d'abstractions.

Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.

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.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

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.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

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.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Une fois que la couche de persistance dépend du domaine, il faut s'inverser maintenant si une dépendance est définie.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(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.


Je ne sais pas ce que cela veut dire, mais cette phrase est incohérente: "Mais selon l'approche que nous adoptons, nous pouvons mal appliquer la dépendance à l'investissement, mais une abstraction." Peut-être aimeriez-vous réparer?
Mark Amery le

9

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.


Même si l'abstraction de haut niveau n'est utile que dans le contexte de cette application, elle peut avoir une valeur lorsque vous souhaitez remplacer l'implémentation réelle par un stub pour le test (ou fournir une interface de ligne de commande à une application généralement disponible sur le Web)
Przemek Pokrywka

3
DIP est important dans tous les langages et n'a rien à voir avec le C ++ lui-même. Même si votre code de haut niveau ne quittera jamais votre application, DIP vous permet de contenir le changement de code lorsque vous ajoutez ou modifiez un code de niveau inférieur. Cela réduit à la fois les coûts de maintenance et les conséquences imprévues du changement. Le DIP est un concept de niveau supérieur. Si vous ne le comprenez pas, vous devez faire plus de recherche sur Google.
Dirk Bester

3
Je pense que vous avez mal compris ce que j'ai dit à propos du C ++. C'était juste une motivation pour DIP; sans doute c'est plus général que cela. Notez que l'article officiel sur DIP indique clairement que la motivation centrale était de soutenir la réutilisation des modules de haut niveau, en les rendant immunisés contre les changements dans les modules de bas niveau; sans besoin de réutilisation, alors il est très probable que ce soit excessif et excessif. (L'avez-vous lu? Il parle également du problème du C ++.)
Rogério

1
N'oubliez pas l'application inverse du DIP? Autrement dit, les modules de niveau supérieur implémentent essentiellement votre application. Votre principale préoccupation n'est pas de la réutiliser, c'est de la rendre moins dépendante des implémentations de modules de niveau inférieur afin que sa mise à jour pour suivre le rythme des changements futurs isole votre application des ravages du temps et des nouvelles technologies. Ce n'est pas pour faciliter le remplacement de WindowsOS, mais pour rendre WindowsOS moins dépendant des détails d'implémentation du FAT / HDD afin que les nouveaux NTFS / SSD puissent être branchés sur WindowsOS avec peu ou pas d'impact.
knockNrod

1
L'écran de l'interface utilisateur n'est certainement pas le module de plus haut niveau ou ne devrait pas être pour aucune application. Les modules de plus haut niveau sont ceux qui contiennent des règles métier. Un module de plus haut niveau n'est pas non plus défini par son potentiel de réutilisation. Veuillez vérifier l'explication simple d'oncle Bob dans cet article: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba

8

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).


Cela peut-il être aussi simple? Il y a de longs articles et même des livres comme DerekGreer l'a mentionné? En fait, je cherchais une réponse simple, mais c'est incroyable si elle est aussi simple: D
Darius.V

1
@ darius-v ce n'est pas une autre version de dire "utiliser des abstractions". Il s'agit de savoir à qui appartiennent les interfaces. Le principal dit que le client (composants de niveau supérieur) doit définir les interfaces et les composants de niveau inférieur doivent les implémenter.
boran

6

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.


6
Comment DIP fait-il exactement cela, pour le code Java ou .NET? Dans ces langages, contrairement au C ++, les modifications apportées à l'implémentation d'un module de bas niveau ne nécessitent pas de modifications dans les modules de haut niveau qui l'utilisent. Seuls les changements dans l'interface publique se répercuteraient, mais l'abstraction définie au niveau supérieur devrait également changer.
Rogério

5

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 Logiccomme le font habituellement les gens:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

vous devriez faire quelque chose comme:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Dataet DataFromDependencydevrait vivre dans le même module que Logic, pas avec Dependency.

Pourquoi faire ceci?

  1. Les deux modules de logique métier sont désormais découplés. Lorsque Dependencyvous changez, vous n'avez pas besoin de changer Logic.
  2. Comprendre ce que Logicfait est une tâche beaucoup plus simple: cela ne fonctionne que sur ce qui ressemble à un ADT.
  3. Logicpeuvent désormais être testés plus facilement. Vous pouvez désormais instancier directement Dataavec de fausses données et les transmettre. Pas besoin de simulacres ou de bancs d'essai complexes.

Je ne pense pas que ce soit juste. Si DataFromDependency, qui référence directement Dependency, se trouve dans le même module que Logic, alors le Logicmodule dépend toujours directement du Dependencymodule 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, Datadevrait être dans le même module que Logic, mais DataFromDependencydevrait être dans le même module que Dependency.
Mark Amery le

3

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:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Code similaire utilisant IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Les avantages d'IoC sont:

  • Vous n'avez aucune dépendance à un framework central, cela peut donc être modifié si vous le souhaitez.
  • Étant donné que les objets sont créés par injection, de préférence à l'aide d'interfaces, il est facile de créer des tests unitaires qui remplacent les dépendances par des versions simulées.
  • Découplage du code.

Le principe d'inversion de dépendance et l'inversion de contrôle sont-ils la même chose?
Peter Mortensen

3
L '"inversion" dans DIP n'est pas la même que dans Inversion of Control. Le premier concerne les dépendances au moment de la compilation, tandis que le second concerne le flux de contrôle entre les méthodes au moment de l'exécution.
Rogério

J'ai l'impression que la version IoC manque quelque chose. Comment est-ce que l'objet de base de données est défini, d'où vient-il. J'essaie de comprendre comment cela ne retarde pas seulement la spécification de toutes les dépendances au plus haut niveau dans une dépendance à un dieu géant
Richard Tingle

1
Comme déjà dit par @ Rogério, DIP n'est pas DI / IoC. Cette réponse est fausse IMO
zeraDev

1

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 ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

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 ...

public class MyClass
{
  public IService myService;
}

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.


2
Cette réponse ne concerne pas vraiment DIP, mais autre chose. Deux morceaux de code peuvent s'appuyer sur une interface abstraite et non l'un sur l'autre, et ne suivent toujours pas le principe d'inversion de dépendance.
Rogério

1

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».


1
C'est une analogie remarquablement bonne. Tout comme, sous le DIC, les composants de haut niveau dépendent encore au moment de l' exécution de ceux de bas niveau, dans cette analogie, l'exécution des plans de haut niveau dépend des plans de bas niveau. Mais aussi, tout comme sous DIC, ce sont les plans de bas niveau qui dépendent au moment de la compilation des plans de haut niveau, c'est le cas dans votre analogie que la formation des plans de bas niveau dépend des plans de haut niveau.
Mark Amery le

0

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:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

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:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}


-1

Disons que nous avons deux classes: Engineeret Programmer:

L'ingénieur de classe a une dépendance sur la classe de programmeur, comme ci-dessous:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

Dans cet exemple, la classe Engineera une dépendance sur notre Programmerclasse. Que se passera-t-il si je dois changer le Programmer?

De toute évidence, je dois changer le Engineeraussi. (Wow, à ce stade OCPest 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 Interfacepour la classe Programmer et à partir de maintenant chaque classe qui veut utiliser le Programmerdoit l'utiliser Interface, 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: DependencyInjectionpeut nous aider à faire DIPet SRPaussi.


-1

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:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

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:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Notez que vous n'avez pas besoin de toucher GoodEncoderpour changer le mode d'E / S - cette classe est satisfaite des interfaces d'E / S qu'elle connaît; toute implémentation de bas niveau IReadableet IWriteablene la dérangera jamais.


Je ne pense pas que ce soit une inversion de dépendance. Après tout, vous n'avez inversé aucune dépendance ici; votre lecteur et votre écrivain ne dépendent pas de GoodEncodervotre 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.
Mark Amery le

-2

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:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

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).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.