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 besitzt, und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für gemeinsam genutzte Ressourcen wie Konfigurationsmanager, Datenbankverbindungen oder Logger.

In Go implementieren wir Singleton mithilfe einer Paketvariablen kombiniert 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
}

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

Verwende Singleton sparsam. Obwohl es praktisch ist, führt es einen globalen Zustand ein, der das Testen erschweren und Abhängigkeiten verbergen kann. Überlege, ob Dependency Injection für deinen Anwendungsfall eine bessere Alternative sein könnte.

challenge icon

Aufgabe

Einfach

Erstellen wir einen Singleton-Logger, der sicherstellt, dass in deiner gesamten Anwendung nur eine Logger-Instanz existiert! Dies ist ein klassischer Anwendungsfall für das Singleton-Muster. Alle Teile deines Programms sollen dieselbe Logger-Konfiguration und denselben Zustand verwenden.

Du organisierst deinen Code auf zwei Dateien verteilt:

  • logger.go: Implementiere deinen threadsicheren Singleton-Logger.

    Erstelle eine Logger-Struktur mit zwei Feldern: Prefix (string) für Präfixe von Log-Nachrichten und MessageCount (int), um zu verfolgen, wie viele Nachrichten protokolliert wurden.

    Verwende Paketvariablen zusammen mit sync.Once, um sicherzustellen, dass nur eine Instanz erstellt wird. Implementiere eine GetLogger()-Funktion, die den Logger mit dem Standardpräfix "[LOG]" initialisiert und die Singleton-Instanz zurückgibt.

    Füge deiner Logger-Struktur die folgenden Methoden hinzu:

    • SetPrefix(prefix string) – ändert das Präfix des Loggers
    • Log(message string) string – erhöht die Anzahl der Nachrichten und gibt eine formatierte Zeichenfolge zurück: [prefix] #[count]: [message]
    • GetCount() int – gibt die Gesamtzahl der protokollierten Nachrichten zurück
  • main.go: Zeige, dass mehrere Aufrufe von GetLogger() dieselbe Instanz zurückgeben.

    Lies einen neuen Präfixwert ein und anschließend eine Anzahl von Nachrichten, gefolgt von jeder zu protokollierenden Nachricht.

    Rufe die Logger-Instanz ab, setze das benutzerdefinierte Präfix, protokolliere anschließend jede Nachricht und gib das Ergebnis aus. Nachdem alle Nachrichten protokolliert wurden, rufe die Logger-Instanz erneut ab (um zu beweisen, dass es dieselbe ist) und gib die Gesamtzahl der Nachrichten aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Zu setzendes benutzerdefiniertes Präfix
  • Zeile 2: Anzahl der Nachrichten
  • Folgende Zeilen: Jede zu protokollierende Nachricht

Beispielsweise bei:

[APP]
3
Server started
User connected
Request processed

sollte deine Ausgabe folgendermaßen aussehen:

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

Und bei:

[DEBUG]
2
Initializing cache
Cache ready

sollte deine Ausgabe folgendermaßen aussehen:

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

Und bei:

[ERROR]
1
Connection failed

sollte deine Ausgabe folgendermaßen aussehen:

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

Die wichtigste Erkenntnis ist, dass du unabhängig davon, wie oft du GetLogger() aufrufst, immer dieselbe Instanz mit ihrem gemeinsamen Zustand erhältst. Die Anzahl der Nachrichten bleibt erhalten, weil nur ein einziger Logger existiert!

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Lese das benutzerdefinierte Präfix
	prefix, _ := reader.ReadString('\n')
	prefix = strings.TrimSpace(prefix)

	// Lese die Anzahl der Nachrichten
	countStr, _ := reader.ReadString('\n')
	countStr = strings.TrimSpace(countStr)
	count, _ := strconv.Atoi(countStr)

	// Lese jede Nachricht
	messages := make([]string, count)
	for i := 0; i < count; i++ {
		msg, _ := reader.ReadString('\n')
		messages[i] = strings.TrimSpace(msg)
	}

	// TODO: Hole die Logger-Instanz mit GetLogger()

	// TODO: Setze das benutzerdefinierte Präfix mit SetPrefix()

	// TODO: Protokolliere jede Nachricht und gib das Ergebnis aus

	// TODO: Hole die Logger-Instanz erneut (um zu beweisen, dass es dieselbe ist)
	// and print the total message count in format: "Total messages logged: X"
}
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

Übe selbstständig: Online-Go-Compiler