Menu
Coddy logo textTech

Wzorzec Singleton

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 86 z 107.

Wzorzec Singleton zapewnia, że typ ma tylko jedną instancję w całym programie, i udostępnia globalny punkt dostępu do niej. Jest to przydatne w przypadku współdzielonych zasobów, takich jak menedżery konfiguracji, połączenia z bazą danych czy rejestratory.

W Go implementujemy wzorzec Singleton za pomocą zmiennej na poziomie pakietu połączonej z sync.Once, aby zagwarantować inicjalizację bezpieczną wątkowo:

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 gwarantuje, że funkcja inicjalizująca zostanie uruchomiona dokładnie raz, nawet gdy wiele gorutyn jednocześnie wywołuje GetInstance(). Każde kolejne wywołanie zwraca tę samą instancję bez ponownego wykonywania kodu inicjalizacyjnego.

cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 i cfg2 wskazują na tę samą instancję

Używaj wzorca Singleton oszczędnie. Choć jest wygodny, wprowadza stan globalny, który może utrudniać testowanie i ukrywać zależności. Zastanów się, czy w Twoim przypadku lepszą alternatywą nie byłoby wstrzykiwanie zależności.

challenge icon

Wyzwanie

Łatwy

Zbudujmy singleton Logger, który zapewnia, że w całej aplikacji istnieje tylko jedna instancja loggera! To klasyczne zastosowanie wzorca Singleton. Chcesz, aby wszystkie części programu korzystały z tej samej konfiguracji i stanu loggera.

Podzielisz kod na dwa pliki:

  • logger.go: Zaimplementuj bezpieczny wątkowo logger Singleton.

    Utwórz strukturę Logger z dwoma polami: Prefix (string) dla prefiksów komunikatów dziennika oraz MessageCount (int), aby śledzić liczbę zarejestrowanych komunikatów.

    Użyj zmiennych na poziomie pakietu wraz z sync.Once, aby zapewnić, że zostanie utworzona tylko jedna instancja. Zaimplementuj funkcję GetLogger(), która inicjalizuje logger z domyślnym prefiksem "[LOG]" i zwraca instancję Singleton.

    Dodaj do Logger następujące metody:

    • SetPrefix(prefix string) - zmienia prefiks loggera
    • Log(message string) string - zwiększa liczbę komunikatów i zwraca sformatowany ciąg: [prefix] #[count]: [message]
    • GetCount() int - zwraca łączną liczbę zarejestrowanych komunikatów
  • main.go: Pokaż, że wielokrotne wywołania GetLogger() zwracają tę samą instancję.

    Wczytaj nową wartość prefiksu, a następnie liczbę komunikatów i po kolei każdy komunikat do zarejestrowania.

    Pobierz instancję loggera, ustaw niestandardowy prefiks, a następnie zarejestruj każdy komunikat i wyświetl wynik. Po zarejestrowaniu wszystkich komunikatów ponownie pobierz instancję loggera (aby udowodnić, że jest to ta sama instancja) i wyświetl łączną liczbę komunikatów.

Dane wejściowe będą miały następującą postać:

  • Wiersz 1: niestandardowy prefiks do ustawienia
  • Wiersz 2: liczba komunikatów
  • Kolejne wiersze: każdy komunikat do zarejestrowania

Na przykład dla danych:

[APP]
3
Server started
User connected
Request processed

Wynik powinien być następujący:

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

A dla danych:

[DEBUG]
2
Initializing cache
Cache ready

Wynik powinien być następujący:

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

A dla danych:

[ERROR]
1
Connection failed

Wynik powinien być następujący:

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

Kluczowa kwestia jest taka, że niezależnie od tego, ile razy wywołasz GetLogger(), zawsze otrzymasz tę samą instancję ze współdzielonym stanem. Liczba komunikatów się utrzymuje, ponieważ istnieje tylko jeden Logger!

Spróbuj swoich sił

package main

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

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

	// Odczytaj niestandardowy prefiks
	prefix, _ := reader.ReadString('\n')
	prefix = strings.TrimSpace(prefix)

	// Odczytaj liczbę wiadomości
	countStr, _ := reader.ReadString('\n')
	countStr = strings.TrimSpace(countStr)
	count, _ := strconv.Atoi(countStr)

	// Odczytaj każdą wiadomość
	messages := make([]string, count)
	for i := 0; i < count; i++ {
		msg, _ := reader.ReadString('\n')
		messages[i] = strings.TrimSpace(msg)
	}

	// TODO: Pobierz instancję loggera za pomocą GetLogger()

	// TODO: Ustaw niestandardowy prefiks za pomocą SetPrefix()

	// TODO: Zaloguj każdą wiadomość i wypisz wynik

	// TODO: Pobierz ponownie instancję loggera (aby wykazać, że jest to ta sama instancja)
	// i wypisz łączną liczbę wiadomości w formacie: "Total messages logged: X"
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online