Dependency Injection en TypeScript : pour débutants
\n\nDans le monde du développement logiciel, on rencontre souvent le terme Dependency Injection (DI), ou injection de dépendances. Pour un développeur débutant en TypeScript, cela peut sembler intimidant, mais il s'agit en réalité d'un modèle simple et puissant qui rend votre code plus propre, plus testable et plus facile à maintenir. Dans cet article, nous allons voir ce qu'est le DI, pourquoi il est nécessaire et comment l'appliquer en pratique avec TypeScript.
\n\nQu'est-ce que l'Injection de Dépendances ?
\n\nImaginez que vous assemblez un ordinateur. Au lieu de souder le processeur directement sur la carte mère en usine, vous laissez un emplacement dans lequel vous pouvez insérer n'importe quel processeur compatible. L'Injection de Dépendances fonctionne sur le même principe : au lieu qu'une classe crée elle-même ses dépendances (par exemple, une base de données ou un client HTTP), elle les reçoit de l'extérieur.
\n\nEn termes simples, le DI est le fait qu'un objet ne crée pas ses "aides" à l'intérieur de lui-même, mais les reçoit toutes prêtes via son constructeur ou une méthode.
\n\nProblème sans DI : Couplage fort
\n\nPrenons un exemple souvent rencontré dans les projets réels :
\n\nclass UserService {\n private database: MySQLDatabase;\n\n constructor() {\n this.database = new MySQLDatabase(); // Couplage fort !\n }\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\nIci, UserService est fortement lié à MySQLDatabase. Si vous souhaitez passer à PostgreSQL ou écrire des tests unitaires avec une base de données factice, vous devrez réécrire toute la classe. Cela viole le principe Open-Closed de SOLID.
Solution avec DI : Flexibilité et testabilité
\n\nRéécrivons maintenant la même classe en utilisant le DI :
\n\ninterface Database {\n findUserById(id: number): User | null;\n}\n\nclass UserService {\n constructor(private database: Database) {} // La dépendance est fournie de l'extérieur\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\n// Utilisation\nconst mySQL = new MySQLDatabase();\nconst service = new UserService(mySQL); // Injection de la dépendance\n\nDésormais, UserService ne sait pas avec quelle base de données il travaille. Vous pouvez facilement remplacer MySQLDatabase par PostgreSQLDatabase ou MockDatabase pour les tests.
Principales méthodes d'injection de dépendances
\n\nEn TypeScript, il existe trois méthodes principales pour injecter des dépendances. Examinons chacune d'elles.
\n\n1. Injection par constructeur (Constructor Injection)
\n\nC'est la méthode la plus courante et recommandée. Les dépendances sont transmises via le constructeur de la classe.
\n\nclass Logger {\n log(message: string) {\n console.log(`[LOG]: ${message}`);\n }\n}\n\nclass OrderService {\n constructor(private logger: Logger) {}\n\n createOrder(data: any) {\n this.logger.log('Order created');\n // logique de création de commande\n }\n}\n\nconst logger = new Logger();\nconst orderService = new OrderService(logger);\n\n2. Injection par propriété (Property Injection)
\n\nLa dépendance est définie directement dans une propriété de l'objet après sa création. Cette méthode est moins utilisée car elle peut entraîner une initialisation incomplète de l'objet.
\n\nclass EmailService {\n sendEmail(to: string, message: string) {\n console.log(`Email sent to ${to}: ${message}`);\n }\n}\n\nclass NotificationService {\n emailService!: EmailService; // Assertion de type\n\n notifyUser(user: string) {\n this.emailService.sendEmail(user, 'Hello!');\n }\n}\n\nconst notification = new NotificationService();\nnotification.emailService = new EmailService(); // Définition de la propriété\nnotification.notifyUser('user@example.com');\n\n