web-dev-qa-db-fra.com

Qu'est-ce que le principe d'inversion de dépendance et pourquoi est-il important?

Qu'est-ce que le principe d'inversion de dépendance et pourquoi est-il important?

163
Phillip Wells

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 doivent jamais dépendre des détails. Les détails devraient dépendre des abstractions.

En résumé: les changements sont risqués et, en fonction d'un concept plutôt que d'une implémentation, vous réduisez le besoin de changement sur les sites d'appels.

Effectivement, le DIP réduit le couplage entre différents éléments de code. L’idée est que, même s’il existe de nombreuses façons de mettre en place une installation d’exploitation forestière, son utilisation 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 développant 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 aussi être intéressant.

102
Carl Seleborg

Les ouvrages Développement de logiciels agiles, Principes, modèles et pratiques, ainsi que Principes, modèles et pratiques agiles en C # sont les meilleures ressources pour comprendre pleinement les objectifs et les motivations initiaux du principe de dépendance. L'article "The Dependency Inversion Principle" est également une bonne ressource, mais comme il s'agit d'une version condensée d'un brouillon qui a finalement été inséré dans les ouvrages mentionnés précédemment, il laisse de côté un débat important sur le concept de La propriété des packages et des interfaces est essentielle pour distinguer ce principe du principe plus général consistant à "programmer pour une interface et non pour une implémentation" du livre Design Patterns (Gamma, et al.).

Pour résumer, le principe d'inversion de dépendance concerne principalement l'inversion du sens conventionnel des dépendances des composants "de niveau supérieur" aux composants "de niveau inférieur" tels que Les composants "de niveau inférieur" dépendent des interfaces appartenant aux composants "de niveau supérieur". (Remarque: le composant "niveau supérieur" désigne ici le 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 réduit tant qu'il est décalé de composants théoriquement moins précieux par rapport à des composants 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, pas la manière dont vous utilisez le composant (par exemple, "INeedSomething", pas "IDoSomething").

Le principe d'inversion de dépendance ne fait pas référence à la pratique simple consistant à résumer des dépendances via des interfaces (par exemple, MyService → [ILogger Logger]). Bien que cela dissocie un composant du détail d'implémentation spécifique de la dépendance, il n'inverse pas la relation entre le consommateur et la dépendance (par exemple, [MyService → IMyServiceLogger] Enregistreur.

L'importance du principe d'inversion de dépendance peut être résumée dans un objectif singulier, qui est 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 cet objectif général de réutilisation, nous pouvons définir 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 toute personne utilisant ce conteneur doit également: utilisez votre 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 identiques dans plusieurs versions d'une application où les détails d'implémentation évoluent).

Avec le premier cas de réutilisation de composants dans plusieurs applications, comme dans une bibliothèque d’infrastructure, l’objectif est de fournir à vos clients un besoin essentiel en infrastructure sans les coupler à des sous-dépendances de votre propre bibliothèque, car prendre des dépendances sur de telles dépendances consommateurs d’exiger les mêmes dépendances. Cela peut poser problème 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 compatible avec la version antérieure. requis par votre bibliothèque.

Dans le second cas de réutilisation de composants de logique métier ("composants de niveau supérieur"), l'objectif est d'isoler la mise en œuvre du domaine principal de votre application des besoins changeants de vos détails d'implémentation (par exemple, modification/mise à niveau de bibliothèques de persistance, bibliothèques de messagerie , stratégies de cryptage, etc.). Idéalement, la modification des détails d'implémentation d'une application ne doit pas casser les composants encapsulant la logique métier de l'application.

Remarque: Certains peuvent s'opposer à la description de ce second cas comme étant une réutilisation réelle, en expliquant que des composants tels que les composants de logique métier utilisés dans une seule application en évolution ne représentent qu'un seul usage. L'idée ici, cependant, est que chaque modification des détails d'implémentation de l'application crée un nouveau contexte et, par conséquent, un cas d'utilisation différent, bien que les objectifs ultimes puissent être distingués de l'isolation par rapport à la portabilité.

Bien que l'application du principe d'inversion des dépendances dans ce deuxième cas puisse présenter certains avantages, il convient de noter que son intérêt pour les langages modernes tels que Java et C # est très réduit, au point même de perdre de sa pertinence. Comme indiqué précédemment, le DIP implique la séparation complète des détails de la mise en œuvre dans des packages distincts. Toutefois, dans le cas d’une application en évolution, le simple fait d’utiliser des interfaces définies en termes de domaine d’activité évitera de devoir modifier des composants de niveau supérieur en raison de l’évolution des besoins en composants de mise en œuvre détaillés, même si les détails de mise en œuvre 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.-à-d. C++) et qui ne concernent pas les langages plus récents. Cela dit, l’importance du principe d’inversion de dépendance repose principalement sur le développement de composants logiciels/bibliothèques réutilisables.

Une discussion plus longue de ce principe en ce qui concerne l'utilisation simple des interfaces, l'injection de dépendance et le modèle d'interface séparée peut être trouvée ici . De plus, une discussion sur la relation entre le principe et les langages à typage dynamique tels que JavaScript peut être foudn here .

137
Derek Greer

Lorsque nous concevons des applications logicielles, nous pouvons considérer les classes de bas niveau, les classes qui implémentent des opérations de base et principales (accès disque, protocoles réseau, ...) et les classes de haut niveau, les classes qui encapsulent une logique complexe (flux d’activités, ...).

Les derniers reposent sur les classes de bas niveau. Une façon naturelle de mettre en place de telles structures serait d'écrire des classes de bas niveau et, une fois que nous les aurons, d'écrire les classes complexes de haut niveau. Puisque les classes de haut niveau sont définies en termes d’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 devraient dépendre des abstractions.

Ce principe cherche à "inverser" la notion conventionnelle selon laquelle les modules de haut niveau dans les logiciels devraient dépendre des modules de niveau inférieur. Ici, les modules de haut niveau possèdent l'abstraction (par exemple, en décidant les méthodes de l'interface) qui sont implémentées par les modules de niveau inférieur. Cela rend les modules de niveau inférieur dépendants des modules de niveau supérieur.

10
nikhil.singhal

L'inversion de dépendance bien appliquée offre souplesse et stabilité au niveau de l'architecture entière de votre application. Cela permettra à votre application d'évoluer de manière plus sécurisée et stable.

Architecture en couches traditionnelle

Traditionnellement, une interface utilisateur à architecture en couches dépendait de la couche métier, ce qui dépendait à son tour de la couche d'accès aux données.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Vous devez comprendre la couche, le package ou la bibliothèque. Voyons comment le code serait.

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 paquet ou de bibliothèque 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 de dépendance

L'inversion de dépendance indique les éléments suivants:

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

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

Quels sont les modules de haut niveau et bas niveau? En pensant aux modules tels que les bibliothèques ou les paquets, les modules de haut niveau seraient ceux qui ont traditionnellement des dépendances et des bas niveaux dont ils dépendent.

En d'autres termes, le module de haut niveau serait l'endroit où l'action est invoquée et le niveau bas où l'action est effectuée.

Une conclusion raisonnable à tirer de ce principe est qu'il ne doit y avoir aucune 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 adaptions notre code comme suit:

Nous aurions une bibliothèque ou un package pour la couche d'accès aux données qui définit l'abstraction.

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

}

Et une autre logique métier de couche de paquet ou de bibliothèque 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 dépendance d'abstraction entre les entreprises et l'accès aux données reste le même.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Pour obtenir une inversion de dépendance, l'interface de persistance doit être définie dans le module ou le package dans lequel se trouve cette logique ou ce domaine de haut niveau et non dans le module de bas niveau.

Définissez d’abord la couche de domaine et l’abstraction de sa communication est la persistance définie.

// Domain.dll
public interface IProductRepository;

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

Une fois que la couche de persistance dépend du domaine, vous pouvez inverser maintenant si une dépendance est définie.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Approfondir le principe

Il est important de bien assimiler le concept, en approfondissant l'objectif 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?

De manière générale, permet aux choses les plus stables, ne dépendant pas de choses moins stables, de changer plus fréquemment.

Il est plus facile de changer le type de persistance, 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 la persistance. De ce fait, 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, raison pour laquelle 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.

Des architectures

Il y a des architectures où l'inversion de dépendance est la clé de sa définition. Dans tous les domaines, c’est le plus important et ce sont les abstractions qui indiqueront le protocole de communication entre le domaine et le reste des packages ou des bibliothèques définis.

Architecture propre

Dans Architecture propre le domaine est situé au centre et si vous regardez dans le sens des flèches indiquant la dépendance, vous voyez clairement quelles sont les couches les plus importantes et les plus stables. Les couches externes sont considérées comme des outils instables, évitez de vous en remettre à cela.

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 à partir du domino. Là encore, il est évident que le domaine est le plus stable et que la dépendance traditionnelle est inversée.

9
xurxodev

Pour moi, le principe d'inversion de dépendance, décrit dans le article officiel , est en réalité une tentative erronée d'augmenter la capacité de réutilisation de modules intrinsèquement moins réutilisables, ainsi qu'un moyen de résoudre un problème en C++. la langue.

Le problème en C++ est que les fichiers d’en-tête contiennent généralement des déclarations de champs privés et de méthodes. Par conséquent, si un module C++ de haut niveau inclut le fichier d’en-tête d’un module de bas niveau, cela dépendra la mise en oeuvre les détails 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 par nature moins réutilisables que les modules de bas niveau, car les premiers sont normalement plus spécifiques à une application/à un contexte que les derniers. Par exemple, un composant qui implémente un écran d'interface utilisateur est du plus haut niveau et également très spécifique (complètement?) À l'application. Essayer de réutiliser un tel composant dans une application différente est contre-productif et ne peut que conduire à une sur-ingénierie.

Ainsi, la création d'une abstraction distincte au même niveau d'un composant A qui dépend d'un composant B (qui ne dépend pas de A) ne peut être effectuée que si le composant A sera réellement utile pour une réutilisation dans différentes applications ou contextes. Si ce n’est pas le cas, l’application de DIP serait une mauvaise conception.

9
Rogério

En gros, il est écrit:

La classe devrait dépendre d'abstractions (par exemple, interface, classes abstraites), pas de détails spécifiques (implémentations).

8
martin.ra

Une manière beaucoup plus claire d'énoncer le principe d'inversion de dépendance est la suivante:

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 d’interfaces de données simples.

C'est-à-dire, au lieu d'implémenter votre classe Logic comme le font d'habitude:

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

Data et DataFromDependency doivent vivre dans le même module que Logic et non pas avec Dependency.

Pourquoi faire ceci?

  1. Les deux modules de logique métier sont maintenant découplés. Lorsque Dependency change, vous n'avez pas besoin de changer Logic
  2. Comprendre ce que Logic fait est une tâche beaucoup plus simple: il ne fonctionne que sur ce qui ressemble à un ADT.
  3. Logic peut maintenant être testé plus facilement. Vous pouvez maintenant instancier directement Data avec de fausses données et les transmettre. Plus besoin de simulacres ni d’échafaudages de test complexes.
5
mattvonb

De bonnes réponses et de bons exemples sont déjà donnés par d'autres ici.

La raison pour laquelle DIP est importante est qu’elle 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 les objets de niveau supérieur, dépendants des objets de niveau inférieur. Les modifications apportées aux 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 niveau supérieur" soient très stables et non fragiles au changement, vous devez donc inverser les dépendances.

5
Hace

L'inversion de contrôle (IoC) est un modèle de conception dans lequel un objet se voit attribuer sa dépendance par un cadre extérieur, plutôt que de demander un cadre pour 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 ne dépendez pas d'un cadre central , Vous pouvez donc le modifier si vous le souhaitez.
  • Les objets étant crééspar 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 Mock.
  • Découplage du code.
5
Staale

Le point d'inversion de dépendance est de créer un logiciel réutilisable.

L'idée est qu'au lieu de deux morceaux de code s'appuyant l'un sur l'autre, ils reposent sur une interface abstraite. Ensuite, vous pouvez réutiliser l'une ou l'autre pièce sans l'autre.

La façon la plus courante d’atteindre cet objectif consiste à inverser le conteneur de contrôle (IoC) comme Spring en Java. Dans ce modèle, les propriétés des objets sont définies via une configuration XML au lieu que les objets sortent et trouvent leur dépendance.

Imaginez ce pseudocode ...

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

MyClass dépend directement de la classe Service et de la classe ServiceLocator. Il faut les deux si vous souhaitez l’utiliser dans une autre application. Maintenant, imaginez ceci ...

public class MyClass
{
  public IService myService;
}

MyClass s'appuie désormais sur une interface unique, 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 entraîner la dépendance de ces deux autres classes.

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.

1
Marc Hughes

Inversion des conteneurs de contrôle et le modèle d'injection de dépendance de Martin Fowler est également une bonne lecture. J'ai trouvé Head First Design Patterns un livre génial pour ma première incursion dans l'apprentissage de la DI et d'autres modèles.

1
Chris Canal

Je pense avoir un exemple bien meilleur (plus intuitif).

  • Imaginez un système (webapp) avec gestion des employés et des contacts (deux écrans).
  • Ils ne sont pas exactement liés, vous voulez donc les placer chacun dans son propre module/dossier

Donc, vous auriez un point d’entrée "principal" qui devrait connaître à la fois le module de gestion des employés et le module de gestion Contact, et il devrait fournir des liens dans la navigation et accepter les demandes d’API, etc. En d’autres termes, le Le module principal dépendrait de ces deux personnes - il connaîtrait leurs contrôleurs, leurs routes et leurs liens qui doivent être rendus dans la navigation (partagée).

Exemple Node.js

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

Aussi, s'il vous plaît noter il existe des cas (simples) lorsque cela est tout à fait bien.


Donc, avec le temps, il arrivera à un moment où il n'est pas si simple d'ajouter de nouveaux modules. Vous devez vous rappeler d'enregistrer api, navigation, peut-être permissions , et ce fichier main.js devient de plus en plus grand.

Et c'est là qu'intervient l'inversion de dépendance. Au lieu que votre module principal soit dépendant de tous les autres modules, vous allez introduire un "noyau" et faire en sorte que chaque module s'enregistre.

Donc, dans ce cas, il s’agit d’avoir la notion de certains ApplicationModule, qui peuvent se soumettre à de nombreux services (routes, navigation, autorisations) et le module principal peut rester simple (il suffit d’importer le module et de le laisser installer)

En d'autres termes, il s'agit de créer une architecture enfichable. C’est un travail supplémentaire et du code que vous devrez écrire/lire et entretenir. Ne le faites pas au début, mais plutôt lorsque vous avez ce genre d’odeur.

Ce qui est particulièrement intéressant, c’est que vous pouvez faire de tout un plugin, même la couche de persistance, ce qui peut être utile si vous avez besoin de supporter de nombreuses implémentations de persistance, mais ce n’est généralement pas le cas. Voir l’autre réponse pour une image à l’architecture hexagonale, c’est une excellente illustration - il existe un noyau et tout le reste est essentiellement un plugin.

0
Kamil Tomšík

Inversion de dépendance: dépend des abstractions et non des concrétions.

Inversion de contrôle: Main vs Abstraction, et comment Main est la colle des systèmes.

DIP and IoC

Voici quelques bons articles sur ce sujet:

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/

Pour ajouter aux nombreuses réponses généralement bonnes, j'aimerais ajouter un échantillon de mon propre échantillon pour démontrer les bonnes pratiques par rapport aux mauvaises. Et oui, je ne suis pas du genre à lancer des pierres!

Disons que vous voulez un petit programme à convertir une chaîne en format base64 via la console d'E/S. Voici l'approche naïve:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-lever 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 en substance que les composants à niveau de levier élevé ne doivent pas dépendre d'une implémentation de bas niveau, où "niveau" est la distance par rapport aux E/S, selon Robert C. Martin ("Architecture propre"). Mais comment sortir de cette situation? Simplement en rendant le codeur central dépendant uniquement des interfaces sans déranger 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 qu'il n'est pas nécessaire de toucher GoodEncoder pour changer le mode d'E/S - cette classe est satisfaite des interfaces d'E/S qu'elle connaît. aucune implémentation de bas niveau de IReadable et IWriteable ne le dérangera jamais.

0
John Silence

En plus d'autres réponses ....

Laissez-moi vous donner un exemple en premier ..

Soit un hôtel qui demande à un générateur de nourriture. L'hôtel donne le nom de la nourriture (par exemple du poulet) au générateur de nourriture et le générateur renvoie la nourriture demandée à l'hôtel. Mais l’hôtel ne se soucie pas du type de nourriture qu’il reçoit et sert. Ainsi, le générateur fournit les aliments avec une étiquette "Aliments" à l'hôtel.

Cette implémentation est en Java

FactoryClass avec une méthode d'usine. Le générateur de nourriture

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Un résumé/Classe d'interface

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


Le poulet met en œuvre la nourriture (une classe de béton)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


Le poisson met en œuvre la nourriture (une classe de béton)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Finalement

L'hôtel

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

Comme vous avez pu le constater, l’hôtel ne sait pas s’il s’agit d’un objet poulet ou d’un objet poisson. Il sait seulement que c’est un objet culinaire, c’est-à-dire que l’hôtel dépend de la classe alimentaire.

Vous remarquerez également que la classe poisson et poulet implémente la classe nourriture et n'est pas directement liée à l'hôtel. Le poulet et le poisson dépendent également de la classe alimentaire.

Ce qui implique que la composante de niveau élevé (Hôtel) et la composante de niveau bas (poisson et poulet) dépendent toutes deux d'une abstraction (Nourriture).

C'est ce qu'on appelle l'inversion de dépendance. 

0
Revolver

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 doivent jamais dépendre de détails. Les détails devraient dépendre des 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 telles que des classes d'interface ou abstraites, pas de détails spécifiques (implémentation de l'interface).

0
Rejwanul Reja