Bases de l'Injection de Dépendances
Fait partie de la section Programmation Orientée Objet du Journey C# de Coddy — leçon 46 sur 70.
L'injection de dépendances (DI) est une technique par laquelle une classe reçoit ses dépendances de l'extérieur plutôt que de les créer elle-même. Cela rend le code plus flexible, testable et faiblement couplé.
Sans l'injection de dépendances (DI), une classe crée ses propres dépendances, ce qui la rend difficile à modifier ou à tester :
public class OrderService
{
private EmailSender sender = new EmailSender(); // Fortement couplé
public void PlaceOrder() => sender.Send("Order placed");
}Avec l'injection de dépendances (DI), les dépendances sont transmises via le constructeur :
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Désormais, OrderService ne sait pas et ne se soucie pas de savoir s'il utilise l'e-mail, le SMS ou un expéditeur fictif pour les tests. La dépendance est « injectée » lors de la création de l'objet :
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email : Commande passéeCe patron suit le principe de dépendance envers les abstractions (interfaces) plutôt que les implémentations concrètes. Il permet d'échanger les implémentations sans modifier la classe qui les utilise, ce qui est essentiel pour les tests unitaires et le maintien d'architectures flexibles.
Défi
FacileConstruisons un système de journalisation qui démontre la puissance de l'injection de dépendances. Au lieu de coder en dur la manière dont les journaux sont écrits, vous allez créer une classe Logger flexible qui peut fonctionner avec n'importe quelle destination de journalisation — console, simulation de fichier, ou n'importe quoi d'autre — sans en connaître les spécificités.
Vous organiserez votre code sur trois fichiers :
ILogWriter.cs: Définissez une interface nomméeILogWriterdans l'espace de nomsLogging. Cette interface représente le contrat pour toute destination de journalisation et doit posséder une seule méthodeWrite(string message)qui retourne une chaîne de caractères décrivant où le message a été écrit.LogWriters.cs: Créez deux implémentations concrètes deILogWriterdans l'espace de nomsLogging:ConsoleLogWriter- sa méthodeWriteretourne"Console: " + messageFileLogWriter- prend un nom de fichier via son constructeur et sa méthodeWriteretourne"File[" + filename + "]: " + message
Logger.cs: Créez une classeLoggerdans l'espace de nomsLoggingqui reçoit sa dépendance par injection de constructeur. LeLoggerdoit :- Accepter un
ILogWritervia son constructeur et le stocker dans un champ privé - Avoir une méthode
Log(string message)qui délègue au rédacteur (writer) injecté et retourne le résultat
- Accepter un
Program.cs: Démontrez l'injection de dépendances en créant deux loggers différents — l'un avec unConsoleLogWriteret l'autre avec unFileLogWriter. Montrez comment la même classeLoggerse comporte différemment selon la dépendance qui est injectée.
Vous recevrez deux entrées :
- Le nom de fichier pour le logger de fichier
- Le message à journaliser
Créez un Logger avec un ConsoleLogWriter et journalisez le message. Ensuite, créez un autre Logger avec un FileLogWriter (en utilisant le nom de fichier provenant de l'entrée) et journalisez le même message. Affichez chaque résultat sur sa propre ligne.
Par exemple, si les entrées sont app.log et System started, la sortie devrait être :
Console: System started
File[app.log]: System startedRemarquez comment la classe Logger ne change jamais — elle fonctionne avec n'importe quelle implémentation de ILogWriter. C'est l'essence même de l'injection de dépendances : le Logger dépend d'une abstraction (l'interface), et l'implémentation concrète est « injectée » de l'extérieur. Vous pourriez facilement ajouter un DatabaseLogWriter ou un CloudLogWriter plus tard sans modifier la classe Logger du tout !
Aide-mémoire
L'injection de dépendances (DI) est une technique par laquelle une classe reçoit ses dépendances de l'extérieur plutôt que de les créer elle-même, ce qui rend le code plus flexible, testable et faiblement couplé.
Sans DI (fortement couplé) :
public class OrderService
{
private EmailSender sender = new EmailSender(); // Tightly coupled
public void PlaceOrder() => sender.Send("Order placed");
}Avec DI (faiblement couplé) :
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Injection de la dépendance :
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedLa DI suit le principe de dépendance envers les abstractions (interfaces) plutôt qu'envers les implémentations concrètes, permettant de changer d'implémentation sans modifier la classe qui les utilise.
Essayez vous-même
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// Lire les entrées
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Créer un Logger avec un ConsoleLogWriter et journaliser le message
// Afficher le résultat
// TODO: Créer un autre Logger avec un FileLogWriter (en utilisant le filename) et journaliser le même message
// Afficher le résultat
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesEspaces de noms et directivesIntro aux classes et objetsLe mot-clé 'this'Méthodes et paramètresChamps vs PropriétésConstructeursInitialiseurs d'objetsRécapitulatif - Calculatrice simple4Héritage
Syntaxe de base de l'héritage (:)Le mot-clé 'base'Mots-clés Virtual & OverrideClasses scelléesLa classe de base 'object'Récapitulatif - Hiérarchie des employés7Fonctionnalités avancées
Surcharge d'opérateursIndexeurs (this[])Redéfinition de ToString()Méthodes d'extensionRécapitulatif - Liste personnalisée10Patrons de conception - Partie 1
Introduction aux patrons de conceptionSingleton Thread-SafePatron FabriquePatron Observateur (Événements)Patron Stratégie2Propriétés et membres statiques
Propriétés auto-implémentéesPropriétés en lecture/écriture seuleChamps et méthodes statiquesClasses statiquesMembres à corps d'expression5Polymorphisme & Interfaces
Polymorphisme : Compilation vs ExécutionInterface vs Classe AbstraiteInterfaces MultiplesInterfaces ExplicitesUpcasting & DowncastingRécapitulatif - Calculateur de Formes8Concepts avancés de la POO
Composition plutôt qu'héritageGénériques (Classes et Méthodes)Délégués et ÉvénementsAttributs et RéflexionIDisposable et l'instruction usingBases de l'Injection de Dépendances11Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode TemplatePatron ÉtatPatron Composite3Architecture des classes
Données d'instance vs statiquesMots-clés 'readonly' & 'const'Champs de supportRécapitulatif - Gestionnaire de compte bancaire6Encapsulation
Modificateurs d'accèsPropriétés pour l'encapsulationImplémentation du masquage de donnéesPatterns d'immuabilitéRécapitulatif - Dossiers d'étudiants12Projet : Système de gestion de bibliothèque
Structure du projetModèles Livre et Utilisateur