Menu
Coddy logo textTech

Injection de dépendances

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy. Leçon 45 sur 107.

L’injection de dépendances est une technique selon laquelle une structure reçoit ses dépendances de l’extérieur plutôt que de les créer en interne. En Go, les interfaces rendent ce modèle naturel et puissant.

Au lieu d’intégrer en dur une implémentation spécifique dans une structure, vous acceptez une interface. Cela vous permet de remplacer les implémentations sans modifier le code de la structure :

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

Créez maintenant une structure qui dépend de l’interface, et non d’un type concret :

type OrderService struct {
    notifier Notifier  // dépendance injectée via l'interface
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

Le OrderService ne sait pas et ne se soucie pas de savoir s’il utilise l’e-mail ou les SMS. Vous injectez la dépendance lors de la création du service :

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email : Commande passée : Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS : Commande passée : Phone
}

Cette approche rend votre code plus flexible et plus facile à tester. Lors des tests, vous pouvez injecter un notificateur fictif qui n’envoie pas réellement de messages. En production, vous injectez l’implémentation réelle. La structure reste inchangée dans les deux scénarios.

challenge icon

Défi

Facile

Construisons un système de journalisation qui démontre la puissance de l’injection de dépendances. Vous allez créer un service capable d’écrire des journaux vers différentes destinations, sans que le service sache ni se soucie de l’endroit où ces journaux sont effectivement envoyés.

Vous allez organiser votre code sur trois fichiers :

  • logger.go : définissez une interface Logger qui exige une méthode Log(message string) string. Créez ensuite deux implémentations différentes de journalisation :
    • ConsoleLogger avec un champ Prefix. Sa méthode Log renvoie [CONSOLE] [Prefix]: [message]
    • FileLogger avec un champ Filename. Sa méthode Log renvoie [FILE:[Filename]] [message]
  • service.go : créez une structure AppService qui dépend de l’interface Logger (et non d’un type concret). Ajoutez une fonction constructeur NewAppService qui accepte un Logger et renvoie un pointeur vers AppService. Donnez à AppService une méthode appelée DoWork(task string) string qui utilise le journal injecté pour journaliser le message Processing: [task] et renvoie ce que le journal renvoie.
  • main.go : lisez la configuration depuis l’entrée, créez les deux types de journaux, injectez chacun d’eux dans des instances séparées de AppService, puis appelez DoWork sur chaque service avec la tâche fournie. Affichez le résultat de chaque appel de service.

Les entrées suivantes seront fournies :

  • Ligne 1 : préfixe du journal de la console
  • Ligne 2 : nom de fichier du journal
  • Ligne 3 : tâche à traiter

Par exemple, avec INFO, app.log et user authentication, votre sortie devrait être :

[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authentication

Remarquez qu’AppService ne sait pas s’il journalise vers une console ou un fichier. Il appelle simplement Log sur le journal qui a été injecté. Cette flexibilité est l’essence de l’injection de dépendances : le même code de service fonctionne avec des implémentations de journalisation complètement différentes.

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lire le préfixe du logger console
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Lire le nom de fichier du logger de fichier
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// Lire la tâche à traiter
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: Créer un ConsoleLogger avec le préfixe
	
	// TODO: Créer un FileLogger avec le nom de fichier
	
	// TODO: Créer un AppService avec le ConsoleLogger injecté
	
	// TODO: Créer un autre AppService avec le FileLogger injecté
	
	// TODO: Appeler DoWork sur chaque service avec la tâche et afficher les résultats
	fmt.Println("result1")
	fmt.Println("result2")
}
quiz iconTestez-vous

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

Entraînez-vous par vous-même : Compilateur Go en ligne