Modello Singleton
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 86 di 107.
Il pattern Singleton garantisce che un tipo abbia una sola istanza nell'intero programma e fornisce un punto di accesso globale a essa. È utile per risorse condivise come gestori della configurazione, connessioni al database o logger.
In Go, implementiamo Singleton usando una variabile a livello di package combinata con sync.Once per garantire un'inizializzazione thread-safe:
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 garantisce che la funzione di inizializzazione venga eseguita esattamente una volta, anche quando più goroutine chiamano GetInstance() contemporaneamente. Ogni chiamata successiva restituisce la stessa istanza senza rieseguire il codice di inizializzazione.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 e cfg2 puntano alla stessa istanzaUsa Singleton con moderazione. Sebbene sia comodo, introduce uno stato globale che può rendere più difficili i test e nascondere le dipendenze. Valuta se l'iniezione delle dipendenze potrebbe essere un'alternativa migliore per il tuo caso d'uso.
Sfida
FacileCostruiamo un singleton Logger che garantisca l'esistenza di una sola istanza del logger in tutta la tua applicazione! Questo è un caso d'uso classico del pattern Singleton. Vuoi che tutte le parti del tuo programma condividano la stessa configurazione e lo stesso stato del logger.
Organizzerai il codice in due file:
logger.go: implementa il tuo logger Singleton thread-safe.Crea una struct
Loggercon due campi:Prefix(string) per i prefissi dei messaggi di log eMessageCount(int) per tenere traccia di quanti messaggi sono stati registrati.Usa variabili a livello di package con
sync.Onceper garantire che venga creata una sola istanza. Implementa una funzioneGetLogger()che inizializza il logger con il prefisso predefinito"[LOG]"e restituisce l'istanza singleton.Aggiungi questi metodi al tuo Logger:
SetPrefix(prefix string)- modifica il prefisso del loggerLog(message string) string- incrementa il conteggio dei messaggi e restituisce una stringa formattata:[prefix] #[count]: [message]GetCount() int- restituisce il numero totale di messaggi registrati
main.go: dimostra che più chiamate aGetLogger()restituiscono la stessa istanza.Leggi un nuovo valore per il prefisso, poi leggi un conteggio di messaggi seguito da ciascun messaggio da registrare.
Ottieni l'istanza del logger, imposta il prefisso personalizzato, poi registra ogni messaggio e stampa il risultato. Dopo aver registrato tutti i messaggi, ottieni di nuovo l'istanza del logger (per dimostrare che è la stessa) e stampa il conteggio totale dei messaggi.
Verranno forniti i seguenti input:
- Riga 1: prefisso personalizzato da impostare
- Riga 2: numero di messaggi
- Righe successive: ogni messaggio da registrare
Per esempio, dato:
[APP]
3
Server started
User connected
Request processedIl tuo output dovrebbe essere:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3E dato:
[DEBUG]
2
Initializing cache
Cache readyIl tuo output dovrebbe essere:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2E dato:
[ERROR]
1
Connection failedIl tuo output dovrebbe essere:
[ERROR] #1: Connection failed
Total messages logged: 1L'intuizione chiave è che, indipendentemente da quante volte chiami GetLogger(), ottieni sempre la stessa istanza con il suo stato condiviso. Il conteggio dei messaggi persiste perché esiste un solo Logger!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il prefisso personalizzato
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Leggi il numero di messaggi
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Leggi ogni messaggio
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: Ottieni l'istanza del logger usando GetLogger()
// TODO: Imposta il prefisso personalizzato usando SetPrefix()
// TODO: Registra ogni messaggio e stampa il risultato
// TODO: Ottieni di nuovo l'istanza del logger (per dimostrare che è la stessa)
// e stampa il numero totale di messaggi nel formato: "Total messages logged: X"
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online