Menu
Coddy logo textTech

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 Instanz

Verwenden 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.

challenge icon

Aufgabe

Einfach

Lassen 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 und MessageCount (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 eine GetLogger()-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 Loggers
    • Log(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 von GetLogger() 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 processed

Ihre Ausgabe sollte sein:

[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3

Und gegeben:

[DEBUG]
2
Initializing cache
Cache ready

Ihre Ausgabe sollte sein:

[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2

Und gegeben:

[ERROR]
1
Connection failed

Ihre Ausgabe sollte sein:

[ERROR] #1: Connection failed
Total messages logged: 1

Die 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 instance

Verwenden 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
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung