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.
Défi
FacileConstruisons 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 interfaceLoggerqui requiert une méthodeLog(message string) string. Ensuite, créez deux implémentations de logger différentes :ConsoleLoggeravec un champPrefix— sa méthodeLogretourne[CONSOLE] [Prefix]: [message]FileLoggeravec un champFilename— sa méthodeLogretourne[FILE:[Filename]] [message]
service.go: Créez une structureAppServicequi dépend de l'interfaceLogger(et non d'un type concret). Incluez une fonction constructeurNewAppServicequi accepte unLoggeret retourne un pointeur versAppService. Donnez àAppServiceune méthode nomméeDoWork(task string) stringqui utilise le logger injecté pour enregistrer le messageProcessing: [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 deAppService, et appelezDoWorksur 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 authenticationRemarquez 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")
}
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 en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool