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.
Wyzwanie
ŁatwyZbudujmy 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ę
Loggerz dwoma polami:Prefix(string) dla prefiksów komunikatów dziennika orazMessageCount(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 loggeraLog(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łaniaGetLogger()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 processedWynik powinien być następujący:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3A dla danych:
[DEBUG]
2
Initializing cache
Cache readyWynik powinien być następujący:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2A dla danych:
[ERROR]
1
Connection failedWynik powinien być następujący:
[ERROR] #1: Connection failed
Total messages logged: 1Kluczowa 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"
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja13Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec Abstract FactoryWzorzec ObserverWzorzec Strategy2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online