Question

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

Était-ce utile?

La solution

Autres conseils

Les livres 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" C’est également une bonne ressource, mais comme il s’agit d’une version condensée d’un brouillon qui a finalement fait son chemin dans les ouvrages susmentionnés, elle laisse de côté une discussion importante sur le concept de propriété du paquet et de l’interface, qui sont la clé du succès. en distinguant ce principe du conseil plus général de "programmation en interface, et non en implémentation" trouvé dans le livre Design Patterns (Gamma, et al.).

Pour résumer, le principe d'inversion de dépendance concerne principalement l'inversion du sens classique des dépendances à partir de "niveau supérieur". composants à " niveau inférieur " des composants tels que " niveau inférieur " les composants dépendent des interfaces possédées par le "niveau supérieur". Composants. (Remarque: le terme "composant de 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 , mais il est décalé des composants qui ont théoriquement moins de valeur que ceux qui ont théoriquement plus de valeur.

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 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 simple pratique consistant à résumer des dépendances via des interfaces (par exemple, MyService & # 8594; [ILogger & # 8656; 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 & # 8594; IMyServiceLogger] & # 8656; Logger.

L’importance du principe d’inversion de dépendance peut être résumée dans un objectif singulier 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 utilise votre conteneur doit également utiliser 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).

En ce qui concerne le premier cas de réutilisation de composants dans plusieurs applications, par exemple dans une bibliothèque d’infrastructure, l’objectif est de fournir à vos clients un besoin en infrastructure essentiel sans les coupler à des sous-dépendances de votre propre bibliothèque. les dépendances exigent que vos consommateurs exigent également 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 deuxième cas de réutilisation de composants de la logique métier ("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 (par exemple, modification / persistance de la persistance bibliothèques, bibliothèques de messagerie, stratégies de cryptage, etc.). Dans l'idéal, la modification des détails d'implémentation d'une application ne doit pas rompre 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. Cependant, l’idée ici est que chaque modification des détails de la mise en œuvre de l’application crée un nouveau contexte et donc un cas d’utilisation différent, bien que les objectifs ultimes puissent être distingués de l’isolation à la portabilité.

Bien que l’application du principe d’inversion de dépendance dans ce deuxième cas puisse présenter certains avantages, il convient de noter que sa valeur, telle qu’elle est appliquée aux langages modernes tels que Java et C #, est très réduite, voire au point d’être dénuée de 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 métier é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 se trouvent 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 sont pas pertinents pour les nouveaux langages. Cela dit, l’importance du principe d’inversion de dépendance réside principalement dans le développement de composants logiciels / bibliothèques réutilisables.

Une discussion plus longue de ce principe en ce qui concerne l'utilisation simple d'interfaces, l'injection de dépendance et le motif 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 expliquée. / "rel =" nofollow noreferrer "> ici .

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 de réseau, ...) et les classes de haut niveau les classes qui encapsulent une logique complexe (flux de travail, .. .).

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 de haut niveau complexes. 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 doivent 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. Rendre ainsi les modules de niveau inférieur dépendants de modules de niveau supérieur.

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

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 des détails de la mise en œuvre 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 de nos jours.

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.

Dependency inversion well applied gives flexibility and stability at the level of the entire architecture of your application. It will allow your application to evolve more securely and stable.

Traditional layered architecture

Traditionally a layered architecture UI depended on the business layer and this in turn depended on the data access layer.

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

You have to understand layer, package, or library. Let's see how the code would be.

We would have a library or package for the data access layer.

// DataAccessLayer.dll
public class ProductDAO {

}

And another library or package layer business logic that depends on the data access layer.

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

Layered architecture with dependency inversion

The dependency inversion indicates the following:

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

What are the high-level modules and low level? Thinking modules such as libraries or packages, high-level module would be those that traditionally have dependencies and low level on which they depend.

In other words, module high level would be where the action is invoked and low level where the action is performed.

A reasonable conclusion to draw from this principle is that there should be no dependence between concretions, but there must be a dependence on an abstraction. But according to the approach we take we can be misapplying investment depend dependency, but an abstraction.

Imagine that we adapt our code as follows:

We would have a library or package for the data access layer which define the abstraction.

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

}

And another library or package layer business logic that depends on the data access layer.

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

Although we are depending on an abstraction dependency between business and data access remains the same.

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

To get dependency inversion, the persistence interface must be defined in the module or package where this high level logic or domain is and not in the low-level module.

First define what the domain layer is and the abstraction of its communication is defined persistence.

// Domain.dll
public interface IProductRepository;

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

After the persistence layer depends on the domain, getting to invert now if a dependency is defined.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

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

Deepening the principle

It is important to assimilate the concept well, deepening the purpose and benefits. If we stay in mechanically and learn the typical case repository, we will not be able to identify where we can apply the principle of dependence.

But why do we invert a dependency? What is the main objective beyond specific examples?

Such commonly allows the most stable things, that are not dependent on less stable things, to change more frequently.

It is easier for the persistence type to be changed, either the database or technology to access the same database than the domain logic or actions designed to communicate with persistence. Because of this, the dependence is reversed because as it is easier to change the persistence if this change occurs. In this way we will not have to change the domain. The domain layer is the most stable of all, which is why it should not depend on anything.

But there is not just this repository example. There are many scenarios where this principle applies and there are architectures based on this principle.

Architectures

There are architectures where dependency inversion is key to its definition. In all the domains it is the most important and it is abstractions that will indicate the communication protocol between the domain and the rest of the packages or libraries are defined.

Clean Architecture

In Clean architecture the domain is located in the center and if you look in the direction of the arrows indicating dependency, it is clear what are the most important and stable layers. The outer layers are considered unstable tools so avoid depending on them.

Hexagonal Architecture

It happens the same way with the hexagonal architecture, where the domain is also located in the central part and ports are abstractions of communication from the domino outward. Here again it is evident that the domain is the most stable and traditional dependence is inverted.

Basically it says:

Class should depend on abstractions (e.g interface, abstract classes), not specific details (implementations).

A much clearer way to state the Dependency Inversion Principle is:

Your modules which encapsulate complex business logic should not depend directly on other modules which encapsulate business logic. Instead, they should depend only on interfaces to simple data.

I.e., instead of implementing your class Logic as people usually do:

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

you should do something like:

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

Data and DataFromDependency should live in the same module as Logic, not with Dependency.

Why do this?

  1. The two business logic modules are now decoupled. When Dependency changes, you don't need to change Logic.
  2. Understanding what Logic does is a much simpler task: it operates only on what looks like an ADT.
  3. Logic can now be more easily tested. You can now directly instantiate Data with fake data and pass it in. No need for mocks or complex test scaffolding.

Good answers and good examples are already given by others here.

The reason DIP is important is because it ensures the OO-principle "loosely coupled design".

The objects in your software should NOT get into a hierarchy where some objects are the top-level ones, dependent on low-level objects. Changes in low-level objects will then ripple-through to your top-level objects which makes the software very fragile for change.

You want your 'top-level' objects to be very stable and not fragile for change, therefore you need to invert the dependencies.

Inversion of control (IoC) is a design pattern where an object gets handed its dependency by an outside framework, rather than asking a framework for its dependency.

Pseudocode example using traditional lookup:

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

Similar code using IoC:

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

The benefits of IoC are:

  • You have no dependency on a central framework, so this can be changed if desired.
  • Since objects are created by injection, preferably using interfaces, it's easy to create unit tests that replace dependencies with mock versions.
  • Decoupling off code.

The point of dependency inversion is to make reusable software.

The idea is that instead of two pieces of code relying on each other, they rely on some abstracted interface. Then you can reuse either piece without the other.

The way this is most commonly achieved is through an inversion of control (IoC) container like Spring in Java. In this model, properties of objects are set up through an XML configuration instead of the objects going out and finding their dependency.

Imagine this pseudocode...

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

MyClass directly depends on both the Service class and the ServiceLocator class. It needs both of those if you want to use it in another application. Now imagine this...

public class MyClass
{
  public IService myService;
}

Now, MyClass relies on a single interface, the IService interface. We'd let the IoC container actually set the value of that variable.

So now, MyClass can easily be reused in other projects, without bringing the dependency of those other two classes along with it.

Even better, you don't have to drag the dependencies of MyService, and the dependencies of those dependencies, and the... well, you get the idea.

Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler is a good read too. I found Head First Design Patterns an awesome book for my first foray into learning DI and other patterns.

Dependency inversion: Depend on abstractions, not on concretions.

Inversion of control: Main vs Abstraction, and how the Main is the glue of the systems.

DIP and IoC

These are some good posts talking about this:

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/

Dependency Inversion Principle (DIP) says that

i) High level modules should not depend upon low-level modules. Both should depend upon abstractions.

ii) Abstractions should never depend upon details. Details should depend upon abstractions.

Example:

    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();
        }
    }

Note: Class should depend on abstractions like interface or abstract classes, not specific details (implementation of interface).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top