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 où une struct reçoit ses dépendances de l'extérieur plutôt que de les créer en interne. En Go, les interfaces rendent ce patron de conception naturel et puissant.

Au lieu de coder en dur une implémentation spécifique à l'intérieur d'une structure, vous acceptez une interface. Cela vous permet de changer d'implémentation 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
}

Maintenant, créez 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 le SMS. Vous injectez la dépendance lors de la création du service :

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email: Order placed: Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Order placed: Phone
}

Cette approche rend votre code plus flexible et plus facile à tester. Pendant les tests, vous pouvez injecter un simulateur de notification (mock notifier) qui n'envoie pas réellement de messages. En production, vous injectez l'implémentation réelle. La struct 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 ne sache ou ne se soucie de l'endroit où ces journaux vont réellement.

Vous organiserez votre code sur trois fichiers :

  • logger.go : Définissez une interface Logger qui requiert une méthode Log(message string) string. Ensuite, créez deux implémentations de logger différentes :
    • ConsoleLogger avec un champ Prefix — sa méthode Log retourne [CONSOLE] [Prefix]: [message]
    • FileLogger avec un champ Filename — sa méthode Log retourne [FILE:[Filename]] [message]
  • service.go : Créez une structure AppService qui dépend de l'interface Logger (et non d'un type concret). Incluez une fonction constructeur NewAppService qui accepte un Logger et retourne un pointeur vers AppService. Donnez à AppService une méthode nommée DoWork(task string) string qui utilise le logger injecté pour enregistrer le message Processing: [task] et retourne ce que le logger renvoie.
  • main.go : Lisez la configuration depuis l'entrée, créez les deux types de logger, injectez chacun d'eux dans des instances distinctes de AppService, et 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 console logger
  • Ligne 2 : Nom de fichier du file logger
  • 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 comment AppService ne sait pas s'il enregistre dans une console ou dans un fichier — il appelle simplement Log sur n'importe quel logger qui a été injecté. Cette flexibilité est l'essence même de l'injection de dépendances : le même code de service fonctionne avec des implémentations de journalisation complètement différentes.

Aide-mémoire

L'injection de dépendances est une technique où 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.

Définissez une interface pour la dépendance :

type Notifier interface {
    Send(message string) string
}

Créez des implémentations concrètes de l'interface :

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 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)
}

Injectez la dépendance lors de la création du service :

emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book"))  // Email: Order placed: Book

smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Order placed: Phone

Cette approche rend le code plus flexible et testable en permettant d'échanger différentes implémentations sans modifier le code de la structure.

Essayez vous-même

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lire le préfixe du logger de console
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Lire le nom du fichier pour le 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 du 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