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.
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 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 interfaceLoggerqui exige une méthodeLog(message string) string. Créez ensuite deux implémentations différentes de journalisation :ConsoleLoggeravec un champPrefix. Sa méthodeLogrenvoie[CONSOLE] [Prefix]: [message]FileLoggeravec un champFilename. Sa méthodeLogrenvoie[FILE:[Filename]] [message]
service.go: créez une structureAppServicequi dépend de l’interfaceLogger(et non d’un type concret). Ajoutez une fonction constructeurNewAppServicequi accepte unLoggeret renvoie un pointeur versAppService. Donnez àAppServiceune méthode appeléeDoWork(task string) stringqui utilise le journal injecté pour journaliser le messageProcessing: [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 deAppService, puis 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 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 authenticationRemarquez 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")
}
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 et non exportésIntroduction à la POO en GoStructs comme classesDéfinir des méthodes sur des structsRécepteurs pointeurs ou par valeurInitialisation des structsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL’interface comme contratInterface vide (any)Assertion de typeCommutation de typeComposition d’interfacesInterfaces Stringer et ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter et SetterDissimulation de l’information en GoRécapitulatif – Fiches d’étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesSolution de contournement pour les méthodes génériquesRécapitulatif - Collection générique2Plongée approfondie dans les types et les structs
Types de base et compositesDéfinitions de types personnalisésTags de structStructs anonymesStructs imbriquésValeurs zéro et valeurs par défautRécapitulatif - Carnet de contacts5La composition plutôt que l’héritage
Pourquoi Go n’a pas d’héritageBases de l’inclusion de structsPromotion des méthodesInclure plusieurs structsInclusion ou agrégationMasquage des méthodes inclusesRécapitulatif : hiérarchie des employés8Gestion des erreurs et POO
L’interface errorTypes d’erreurs personnalisésEnrobage des erreurs (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - Analyseur de fichiers11Bibliothèque standard et POO
io.Reader et io.Writersort.InterfaceInterface fmt.Stringerencoding/json avec des structsInterface http.HandlerRécapitulatif - Modèles d’API REST14Modèles de conception – Partie 2
Modèle CommandeModèle AdaptateurModèle DécorateurModèle Méthode modèleModèle ÉtatModèle CompositeMiddleware comme décorateur3Pointeurs et mémoire
Notions de base des pointeurs en GoPointeurs vers des structuresPassage par valeur ou par référenceLa fonction new()Garbage collection en GoRécapitulatif - Constructeur de listes chaînées6Polymorphisme en Go
Polymorphisme via les interfacesDuck typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif – Processeur de paiements9Concurrence et POO
Bases des GoroutinesCanaux et communicationCanaux tamponnés ou non tamponnésInstruction selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures thread-safeRécapitulatif - Pool de workersEntraînez-vous par vous-même : Compilateur Go en ligne