Menu
Coddy logo textTech

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 istanza

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

challenge icon

Sfida

Facile

Costruiamo 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 Logger con due campi: Prefix (string) per i prefissi dei messaggi di log e MessageCount (int) per tenere traccia di quanti messaggi sono stati registrati.

    Usa variabili a livello di package con sync.Once per garantire che venga creata una sola istanza. Implementa una funzione GetLogger() 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 logger
    • Log(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 a GetLogger() 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 processed

Il tuo output dovrebbe essere:

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

E dato:

[DEBUG]
2
Initializing cache
Cache ready

Il tuo output dovrebbe essere:

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

E dato:

[ERROR]
1
Connection failed

Il tuo output dovrebbe essere:

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

L'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"
}
quiz iconMettiti alla prova

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

Esercitati da solo: Compilatore Go online