Introduction : Qu'est-ce que l'Injection de Dépendances et pourquoi en a-t-on besoin en Kotlin ?
\nL'Injection de Dépendances (DI) est un modèle de conception qui permet de rendre le code plus flexible, testable et maintenable. Au lieu qu'un objet crée lui-même ses dépendances, la DI les transmet (injecte) de l'extérieur. En Kotlin, avec sa syntaxe concise et ses puissantes fonctionnalités, la DI devient un outil particulièrement élégant.
\nImaginez que vous écrivez une application pour commander une pizza. Vous avez une classe OrderService qui doit envoyer des notifications. Sans DI, elle ressemblerait à ceci :
class OrderService {\n private val notifier = EmailNotifier() // Couplage fort\n\n fun placeOrder(order: Order) {\n // Logique de commande\n notifier.send("Votre commande a été acceptée !")\n }\n}\nProblème : vous ne pouvez pas facilement remplacer EmailNotifier par SmsNotifier ni tester OrderService sans envoyer réellement d'e-mails. La DI résout ce problème en injectant la dépendance via le constructeur :
class OrderService(private val notifier: Notifier) {\n fun placeOrder(order: Order) {\n notifier.send("Votre commande a été acceptée !")\n }\n}\nMaintenant, OrderService ne sait pas comment la notification est envoyée. C'est l'essence de la DI : un couplage faible et une réutilisabilité élevée.
Principales façons d'implémenter la DI en Kotlin
\nEn Kotlin, il existe trois approches principales pour l'injection de dépendances : la DI manuelle, l'utilisation de bibliothèques (Koin, Dagger 2) et l'injection via des frameworks (par exemple, Spring). Examinons chacune en pratique.
\n\n1. Injection manuelle de dépendances (Manual DI)
\nLa manière la plus simple est de transmettre les dépendances via le constructeur ou les setters. Cela ne nécessite pas de bibliothèques supplémentaires et est idéal pour les petits projets.
\nExemple : Créons un service simple pour traiter les paiements.
\n// Interface de passerelle de paiement\ninterface PaymentGateway {\n fun process(amount: Double): Boolean\n}\n\n// Implémentation via PayPal\nclass PayPalGateway : PaymentGateway {\n override fun process(amount: Double): Boolean {\n println("Traitement du paiement $amount via PayPal")\n return true\n }\n}\n\n// Service de commandes avec injection de dépendance\nclass OrderService(private val paymentGateway: PaymentGateway) {\n fun checkout(total: Double) {\n if (paymentGateway.process(total)) {\n println("Commande finalisée !")\n }\n }\n}\n\n// Point d'entrée — "fabrique" ou "conteneur"\nfun main() {\n val gateway = PayPalGateway()\n val service = OrderService(gateway)\n service.checkout(100.0)\n}\nAvantages : Simplicité, contrôle total, aucune dépendance aux bibliothèques.
\nInconvénients : Lorsque le projet grandit, la gestion manuelle de toutes les dépendances devient complexe (il faut créer un "conteneur" ou une "fabrique" manuellement).
2. Injection de dépendances avec Koin
\nKoin est une bibliothèque légère pour la DI, écrite en Kotlin. Elle n'utilise pas la réflexion et ne nécessite pas de génération de code, ce qui la rend idéale pour les projets petits et moyens.
\nInstallation : Ajoutez dans build.gradle.kts :
implementation("io.insert-koin:koin-core:3.5.6")\nExemple d'utilisation :
\nimport org.koin.core.context.startKoin\nimport org.koin.dsl.module\n\n// Module de dépendances\nval appModule = module {\n single<PaymentGateway> { PayPalGateway() }\n factory { OrderService(get()) } // get() récupère automatiquement PaymentGateway\n}\n\nfun main() {\n startKoin {\n modules(appModule)\n }\n\n // Récupération du service depuis le conteneur\n val service: OrderService = org.koin.core.component.get()\n service.checkout(200.0)\n}\nConcepts clés de Koin :
\n- \n
single— crée une seule instance (singleton)