Singleton-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 86 von 107.
Das Singleton-Muster stellt sicher, dass ein Typ während des gesamten Programms nur eine Instanz hat und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für gemeinsam genutzte Ressourcen wie Konfigurationsmanager, Datenbankverbindungen oder Logger.
In Go implementieren wir das Singleton-Muster mithilfe einer Variable auf Paketebene in Kombination mit sync.Once, um eine threadsichere Initialisierung zu gewährleisten:
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
}Das sync.Once garantiert, dass die Initialisierungsfunktion genau einmal ausgeführt wird, selbst wenn mehrere Goroutinen gleichzeitig GetInstance() aufrufen. Jeder nachfolgende Aufruf gibt dieselbe Instanz zurück, ohne den Initialisierungscode erneut auszuführen.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 und cfg2 zeigen auf dieselbe InstanzVerwenden Sie Singleton sparsam. Obwohl es praktisch ist, führt es einen globalen Zustand ein, der das Testen erschweren und Abhängigkeiten verbergen kann. Überlegen Sie, ob Dependency Injection eine bessere Alternative für Ihren Anwendungsfall sein könnte.
Aufgabe
EinfachLassen Sie uns einen Logger-Singleton erstellen, der sicherstellt, dass während der gesamten Laufzeit Ihrer Anwendung nur eine einzige Logger-Instanz existiert! Dies ist ein klassischer Anwendungsfall für das Singleton-Muster – Sie möchten, dass alle Teile Ihres Programms dieselbe Logger-Konfiguration und denselben Status teilen.
Sie werden Ihren Code auf zwei Dateien aufteilen:
logger.go: Implementieren Sie Ihren threadsicheren Singleton-Logger.Erstellen Sie ein
Logger-Struct mit zwei Feldern:Prefix(string) für Präfixe von Log-Nachrichten undMessageCount(int), um zu verfolgen, wie viele Nachrichten protokolliert wurden.Verwenden Sie Variablen auf Paketebene mit
sync.Once, um sicherzustellen, dass jemals nur eine Instanz erstellt wird. Implementieren Sie eineGetLogger()-Funktion, die den Logger mit einem Standard-Präfix von"[LOG]"initialisiert und die Singleton-Instanz zurückgibt.Fügen Sie Ihrem Logger diese Methoden hinzu:
SetPrefix(prefix string)- ändert das Präfix des LoggersLog(message string) string- erhöht den Nachrichtenzähler und gibt einen formatierten String zurück:[prefix] #[count]: [message]GetCount() int- gibt die Gesamtzahl der protokollierten Nachrichten zurück
main.go: Demonstrieren Sie, dass mehrere Aufrufe vonGetLogger()dieselbe Instanz zurückgeben.Lesen Sie einen neuen Präfix-Wert ein, lesen Sie dann die Anzahl der Nachrichten und anschließend jede zu protokollierende Nachricht ein.
Holen Sie sich die Logger-Instanz, setzen Sie das benutzerdefinierte Präfix, protokollieren Sie dann jede Nachricht und geben Sie das Ergebnis aus. Nachdem alle Nachrichten protokolliert wurden, holen Sie sich die Logger-Instanz erneut (um zu beweisen, dass es dieselbe ist) und geben Sie die Gesamtzahl der Nachrichten aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Benutzerdefiniertes Präfix, das gesetzt werden soll
- Zeile 2: Anzahl der Nachrichten
- Folgende Zeilen: Jede zu protokollierende Nachricht
Zum Beispiel, gegeben:
[APP]
3
Server started
User connected
Request processedIhre Ausgabe sollte sein:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3Und gegeben:
[DEBUG]
2
Initializing cache
Cache readyIhre Ausgabe sollte sein:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2Und gegeben:
[ERROR]
1
Connection failedIhre Ausgabe sollte sein:
[ERROR] #1: Connection failed
Total messages logged: 1Die entscheidende Erkenntnis ist, dass Sie, egal wie oft Sie GetLogger() aufrufen, immer dieselbe Instanz mit ihrem gemeinsamen Status erhalten. Der Nachrichtenzähler bleibt bestehen, weil es nur einen existierenden Logger gibt!
Spickzettel
Das Singleton-Muster stellt sicher, dass ein Typ während des gesamten Programms nur eine einzige Instanz hat, und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für gemeinsam genutzte Ressourcen wie Konfigurationsmanager, Datenbankverbindungen oder Logger.
Implementieren Sie das Singleton unter Verwendung einer Variable auf Paketebene in Kombination mit sync.Once für eine threadsichere Initialisierung:
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
}Das sync.Once garantiert, dass die Initialisierungsfunktion genau einmal ausgeführt wird, selbst wenn mehrere Goroutinen GetInstance() gleichzeitig aufrufen. Jeder nachfolgende Aufruf gibt dieselbe Instanz zurück:
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 and cfg2 point to the same instanceVerwenden Sie das Singleton sparsam, da es einen globalen Zustand einführt, der das Testen erschweren und Abhängigkeiten verbergen kann. Erwägen Sie Dependency Injection als Alternative.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Den benutzerdefinierten Präfix lesen
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Die Anzahl der Nachrichten lesen
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Jede Nachricht lesen
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: Die Logger-Instanz mit GetLogger() abrufen
// TODO: Den benutzerdefinierten Präfix mit SetPrefix() setzen
// TODO: Jede Nachricht protokollieren und das Ergebnis ausgeben
// TODO: Die Logger-Instanz erneut abrufen (um zu beweisen, dass es dieselbe ist)
// und die Gesamtzahl der Nachrichten im Format "Total messages logged: X" ausgeben
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterAbstract-Factory-MusterObserver-MusterStrategy-Muster2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool