Patron Singleton
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 86 sur 107.
Le pattern Singleton garantit qu'un type n'a qu'une seule instance tout au long de votre programme et fournit un point d'accès global à celle-ci. C'est utile pour les ressources partagées comme les gestionnaires de configuration, les connexions aux bases de données ou les journaux (loggers).
En Go, nous implémentons le Singleton en utilisant une variable au niveau du package combinée avec sync.Once pour garantir une initialisation thread-safe :
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}Le sync.Once garantit que la fonction d'initialisation s'exécute exactement une fois, même lorsque plusieurs goroutines appellent GetInstance() simultanément. Chaque appel ultérieur retourne la même instance sans réexécuter le code d'initialisation.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 et cfg2 pointent vers la même instanceUtilisez le Singleton avec parcimonie. Bien que pratique, il introduit un état global qui peut rendre les tests plus difficiles et masquer les dépendances. Considérez si l'injection de dépendances pourrait être une meilleure alternative pour votre cas d'utilisation.
Défi
FacileConstruisons un singleton Logger qui garantit qu'une seule instance de logger existe dans toute votre application ! C'est un cas d'utilisation classique pour le motif Singleton — vous voulez que toutes les parties de votre programme partagent la même configuration et le même état de logger.
Vous organiserez votre code sur deux fichiers :
logger.go: Implémentez votre logger Singleton thread-safe.Créez une structure
Loggeravec deux champs :Prefix(string) pour les préfixes des messages de log, etMessageCount(int) pour suivre le nombre de messages enregistrés.Utilisez des variables au niveau du package avec
sync.Oncepour garantir qu'une seule instance est créée. Implémentez une fonctionGetLogger()qui initialise le logger avec un préfixe par défaut de"[LOG]"et retourne l'instance singleton.Ajoutez ces méthodes à votre Logger :
SetPrefix(prefix string)- modifie le préfixe du loggerLog(message string) string- incrémente le compteur de messages et retourne une chaîne formatée :[prefix] #[count]: [message]GetCount() int- retourne le nombre total de messages enregistrés
main.go: Démontrez que plusieurs appels àGetLogger()retournent la même instance.Lisez une nouvelle valeur de préfixe, puis lisez un nombre de messages suivi de chaque message à enregistrer.
Récupérez l'instance du logger, définissez le préfixe personnalisé, puis enregistrez chaque message et affichez le résultat. Après avoir enregistré tous les messages, récupérez à nouveau l'instance du logger (pour prouver qu'il s'agit de la même) et affichez le nombre total de messages.
Les entrées suivantes seront fournies :
- Ligne 1 : Préfixe personnalisé à définir
- Ligne 2 : Nombre de messages
- Lignes suivantes : Chaque message à enregistrer
Par exemple, étant donné :
[APP]
3
Server started
User connected
Request processedVotre sortie devrait être :
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3Et étant donné :
[DEBUG]
2
Initializing cache
Cache readyVotre sortie devrait être :
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2Et étant donné :
[ERROR]
1
Connection failedVotre sortie devrait être :
[ERROR] #1: Connection failed
Total messages logged: 1L'idée clé est que peu importe le nombre de fois que vous appelez GetLogger(), vous obtenez toujours la même instance avec son état partagé. Le compteur de messages persiste car il n'existe qu'un seul Logger !
Aide-mémoire
Le pattern Singleton garantit qu'un type ne possède qu'une seule instance tout au long de votre programme et fournit un point d'accès global à celle-ci. Cela est utile pour les ressources partagées telles que les gestionnaires de configuration, les connexions aux bases de données ou les journaux (loggers).
Implémentez le Singleton en utilisant une variable au niveau du package combinée avec sync.Once pour une initialisation sécurisée au niveau des threads (thread-safe) :
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}Le sync.Once garantit que la fonction d'initialisation ne s'exécute qu'une seule fois, même lorsque plusieurs goroutines appellent GetInstance() simultanément. Chaque appel ultérieur renvoie la même instance :
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 and cfg2 point to the same instanceUtilisez le Singleton avec parcimonie car il introduit un état global qui peut rendre les tests plus difficiles et masquer les dépendances. Envisagez l'injection de dépendances comme alternative.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le préfixe personnalisé
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Lire le nombre de messages
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Lire chaque message
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: Obtenir l'instance du logger en utilisant GetLogger()
// TODO: Définir le préfixe personnalisé en utilisant SetPrefix()
// TODO: Enregistrer chaque message et afficher le résultat
// TODO: Obtenir à nouveau l'instance du logger (pour prouver que c'est la même)
// et afficher le nombre total de messages au format : "Total messages logged: X"
}
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