DIP : principes de base, inversion des dépendances dans le développement

Auteur : IT Sectr Publié le : 2026-05-12 Temps de lecture : 9 min

DIP (Dependency Inversion Principle) — le cinquième principe SOLID qui définit les règles de construction des dépendances entre modules : les modules de haut niveau ne doivent pas dépendre des modules de bas niveau, les deux doivent dépendre d'abstractions. Les abstractions ne doivent pas dépendre des détails — les détails doivent dépendre des abstractions. Ce principe, décrit par Robert Martin dans Clean Architecture (2017), est à la base de l'architecture faiblement couplée. Selon ce livre, le principe d'inversion des dépendances élimine les couplages rigides entre les couches de l'application.

Points clés

  • DIP — principe d'inversion des dépendances, le cinquième de SOLID, sur les limites architecturales
  • Les modules de haut niveau ne doivent pas importer les modules de bas niveau — seulement les abstractions
  • DIP ≠ DI : Dependency Inversion est un principe architectural, Dependency Injection est une façon de l'implémenter
  • Les abstractions appartiennent au module de haut niveau, les implémentations à celui de bas niveau
  • DIP inverse la hiérarchie traditionnelle des dépendances dans les architectures multicouches

Qu'est-ce que le DIP (Dependency Inversion Principle) ?

DIP (Dependency Inversion Principle) est le principe d'inversion des dépendances qui inverse la vision traditionnelle de la direction des dépendances entre modules. Les modules de haut niveau (logique métier) ne doivent pas dépendre directement des modules de bas niveau (base de données, réseau, UI). Au lieu de cela, les deux niveaux dépendent d'abstractions définies dans le module de haut niveau.

La formulation formelle du DIP comprend deux règles : A — les modules de haut niveau ne doivent pas dépendre des modules de bas niveau, les deux doivent dépendre d'abstractions. B — les abstractions ne doivent pas dépendre des détails, les détails doivent dépendre des abstractions. La deuxième règle découle de la première : si une abstraction dépend de détails, elle ne peut pas être une base stable pour un module de haut niveau.

Sans DIP, une architecture typique ressemble à ceci : BusinessLogic → DatabaseRepository — la logique métier dépend directement d'un référentiel concret. Avec DIP : BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic ne connaît pas l'existence de DatabaseRepository, il connaît seulement l'interface DatabaseService, qui est implémentée en dehors de la logique métier.

Direction des dépendances dans DIP

L'inversion signifie que le flux de contrôle et le flux de dépendances vont dans des directions opposées. Le flux de contrôle va de haut en bas : UI → ViewModel → UseCase → Repository. Le flux de dépendances va de bas en haut : Repository implémente une interface définie dans UseCase. Repository (bas niveau) dépend de UseCase (haut niveau).

Cette inversion est la différence clé entre DIP et la séparation habituelle en couches. Dans l'architecture traditionnelle en couches, chaque couche dépend de la couche inférieure. Dans l'architecture avec DIP, toutes les couches dépendent d'abstractions, tandis que l'implémentation de ces abstractions réside dans la couche d'infrastructure, qui est « branchée » aux couches supérieures via des mécanismes de DI.

Comment fonctionne le principe d'inversion des dépendances

Le mécanisme DIP est implémenté en définissant des abstractions dans les modules de haut niveau et en les réalisant dans les modules de bas niveau. Le module de haut niveau déclare une interface pour la fonctionnalité dont il a besoin. Le module de bas niveau implémente cette interface. Le câblage (wiring) se produit à la racine de composition de l'application.

Le processus d'introduction du DIP dans du code existant : extraire une interface pour le module de bas niveau, déplacer cette interface vers le module de haut niveau (ou vers une couche d'abstraction séparée), réécrire la dépendance du module de haut niveau pour utiliser l'interface, faire implémenter cette interface par le module de bas niveau. Après ces étapes, la direction de la dépendance a été inversée.

DIP nécessite un mécanisme de racine de composition — un point dans l'application où toutes les dépendances sont créées et liées entre elles. Sous Android, c'est Application.get() ou le composant Hilt, sous iOS — AppDelegate ou SceneDelegate. La racine de composition est le seul endroit où le code connaît les implémentations concrètes.

Isolation des couches via DIP

DIP crée des limites architecturales entre les couches de l'application. Lorsque ViewModel dépend de l'interface UserRepository, une limite se forme entre les couches de présentation et de domaine : ViewModel (présentation) ne sait pas d'où viennent les données. Cette limite permet de changer l'implémentation de UserRepository (Room → REST → Mock) sans affecter ViewModel. Plus il y a de telles limites, plus l'application est résiliente aux changements de frameworks et de bibliothèques.

Dans l'architecture Android recommandée par Google, DIP est implémenté via des UseCases qui résident dans la couche domaine et dépendent d'interfaces Repository. RepositoryImpl se trouvent dans la couche données et implémentent ces interfaces. La couche présentation (ViewModel) dépend des UseCases. La direction des dépendances va de la présentation vers le domaine, du domaine vers les données — mais aucune couche ne connaît les implémentations concrètes d'une autre couche.

Différence entre DIP et DI (Dependency Injection)

DIP et DI sont souvent confondus, mais ce sont des concepts différents. DIP est un principe architectural (CE QU'il faut faire : dépendre d'abstractions). DI est un modèle d'implémentation (COMMENT le faire : passer les dépendances via le constructeur). DIP répond à la question « sur quoi les modules doivent-ils se baser ? », DI répond à « comment les objets obtiennent-ils leurs dépendances ? ».

L'injection de dépendances (Dependency Injection) est une façon d'injecter des dépendances dans un objet via le constructeur, une méthode ou une propriété. Lorsqu'une classe Kotlin reçoit une interface Repository via son constructeur — c'est du DI. Le fait qu'une classe ViewModel dépende de l'interface Repository et non d'une implémentation concrète de RoomRepository — c'est du DIP. DI est l'outil, DIP est l'objectif.

On peut suivre DIP sans framework DI : le câblage manuel des dépendances dans la racine de composition est aussi du DI (DI manuel). On peut utiliser un framework DI (Dagger, Hilt, Koin) en violant DIP : si ViewModel crée directement un objet Repository via new() — DIP est violé, même si le framework est installé. DIP est une décision architecturale, DI est un détail technique.

Exemples de DIP dans le développement mobile

Prenons un exemple Android d'application de DIP à la couche de données. Sans DIP, une ViewModel crée directement une RoomDatabase et DAO. Avec DIP — la ViewModel dépend de l'interface UserRepository, et l'implémentation concrète RoomUserRepository est fournie de l'extérieur.

kotlin
// L'abstraction appartient à la couche domaine (haut niveau)
interface UserRepository {
    fun getUser(id: Int): User
}

// La couche domaine dépend uniquement de l'abstraction
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// L'implémentation dans la couche données dépend de l'abstraction de la couche domaine
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// Racine de composition
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

Un exemple iOS avec un Application Coordinator et un protocole de navigation :

swift
// Abstraction de navigation dans la couche domaine
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel dépend de l'abstraction, pas d'UIKit
final class AuthViewModel {
    private let navigation: AuthNavigation

    init(navigation: AuthNavigation) {
        self.navigation = navigation
    }

    func onLoginSuccess() {
        navigation.navigateToHome()
    }
}

// Coordinator (couche UIKit) implémente le protocole de la couche domaine
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // Code de navigation UIKit
    }
    func navigateToLogin() {
        // Code de navigation UIKit
    }
}

Le point clé : AuthViewModel (domaine) ne connaît pas l'existence d'AppCoordinator (UIKit). Il connaît seulement le protocole AuthNavigation. Si UIKit est remplacé par SwiftUI demain — AuthViewModel ne nécessite aucune modification. DIP rend la couche domaine indépendante des frameworks et bibliothèques d'UI.

Outils pour DIP : Dagger, Hilt, Koin

Hilt est l'outil DI standard pour Android, recommandé par Google. Il est intégré à Jetpack, prend en charge ViewModel, Fragment, Service et d'autres composants Android. Hilt automatise la création de la racine de composition via les annotations @Module, @Provides, @Inject. Utiliser Hilt ne garantit pas le respect du DIP — l'interface UserRepository doit être définie dans la couche domaine, pas dans la couche données.

Koin est un framework DI léger pour Kotlin sans génération de code ni traitement d'annotations. Le DSL de Koin (module, single, factory) est plus facile à apprendre, mais la vérification des dépendances se fait à l'exécution et non à la compilation. Koin est populaire dans les projets multiplateformes (KMP) grâce à la prise en charge d'iOS.

Dagger 2 est le prédécesseur de Hilt, encore utilisé dans les grands projets. Dagger génère du code DI à la compilation, offrant des performances maximales et un diagnostic d'erreurs au moment de la construction. Hilt est construit sur Dagger et fournit une API simplifiée. Pour les nouveaux projets, Google recommande Hilt comme framework DI principal.

Organisation des modules DI par couches

Les modules DI doivent correspondre aux couches architecturales et être séparés en DomainModule, DataModule, PresentationModule. DomainModule fournit uniquement des abstractions et des UseCases. DataModule fournit des implémentations pour les abstractions. PresentationModule relie les ViewModels aux UseCases. Cette organisation garantit que la couche domaine reste indépendante des bibliothèques d'infrastructure.

Lors de la migration entre frameworks DI (par exemple, de Koin vers Hilt), la structure de DomainModule ne change pas — seules les méthodes de câblage dans DataModule et PresentationModule changent. DIP assure l'isolation de la logique domaine, tandis que le framework DI est un mécanisme technique de câblage.

Foire aux questions

Faut-il toujours appliquer DIP ?

DIP est nécessaire aux limites architecturales — entre les couches de l'application (domaine → données, présentation → domaine). Au sein d'une même couche, DIP peut être excessif. Par exemple, une classe utilitaire StringFormatter dans la couche domaine ne nécessite pas d'interface — s'il n'y a pas de raison de la remplacer.

DIP est-il la même chose que l'injection de dépendances ?

Non. DIP est un principe : les modules doivent dépendre d'abstractions. DI est un modèle : un objet reçoit ses dépendances de l'extérieur plutôt que de les créer lui-même. DI est une façon d'implémenter DIP, mais DIP peut être suivi sans DI (via des fabriques ou un service locator). DI sans DIP est possible mais n'a aucune valeur architecturale.

Où définir les interfaces pour DIP ?

Les interfaces appartiennent au module qui les utilise, pas au module qui les implémente. UserRepository est déclaré dans la couche domaine et implémenté dans la couche données. C'est la règle clé de DIP : le propriétaire de l'abstraction est le consommateur, pas le fournisseur de l'implémentation.

Comment DIP affecte-t-il les tests ?

DIP rend les tests possibles sur des couches isolées. Une ViewModel qui dépend de UserRepository (interface) peut être testée avec une implémentation mock sans base de données. Sans DIP, la ViewModel dépendrait de RoomUserRepository et nécessiterait une configuration de base de données pour chaque test. DIP + DI offrent une isolation complète des modules lors des tests.

Quel framework DI choisir pour Android ?

Hilt est le choix standard pour les projets Android, recommandé par Google. Koin est une alternative pour les projets Kotlin Multiplatform. Dagger 2 est pour les projets existants où la migration vers Hilt n'est pas justifiée. Le choix du framework ne supprime pas la nécessité de suivre DIP au niveau architectural.

Résumé

  • DIP (Dependency Inversion Principle) — le cinquième principe SOLID sur les limites architecturales via les abstractions
  • Les modules de haut niveau ne dépendent pas des modules de bas niveau — les deux dépendent d'abstractions
  • DIP ≠ DI : principe vs modèle d'implémentation ; DI est l'outil, DIP est l'objectif
  • Les abstractions appartiennent au consommateur (couche domaine), pas au fournisseur (couche données)
  • Racine de composition — le seul endroit dans l'application où les dépendances concrètes sont assemblées
  • Hilt, Koin, Dagger — outils DI qui automatisent le câblage mais ne remplacent pas la décision architecturale du DIP
  • La couche domaine construite selon DIP reste indépendante des frameworks, de l'UI et des bibliothèques d'infrastructure

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi