Patrón Singleton
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 86 de 107.
El patrón Singleton garantiza que un tipo tenga una sola instancia en todo el programa y proporciona un punto de acceso global a ella. Esto es útil para recursos compartidos como gestores de configuración, conexiones a bases de datos o registradores (loggers).
En Go, implementamos el patrón Singleton utilizando una variable a nivel de paquete combinada con sync.Once para garantizar una inicialización segura para hilos (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
}El sync.Once garantiza que la función de inicialización se ejecute exactamente una vez, incluso cuando múltiples goroutines llaman a GetInstance() simultáneamente. Cada llamada posterior devuelve la misma instancia sin volver a ejecutar el código de inicialización.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 y cfg2 apuntan a la misma instanciaUsa Singleton con moderación. Aunque es conveniente, introduce un estado global que puede dificultar las pruebas y ocultar dependencias. Considera si la inyección de dependencias podría ser una mejor alternativa para tu caso de uso.
Desafío
Fácil¡Vamos a construir un singleton Logger que asegure que solo exista una instancia de logger en toda tu aplicación! Este es un caso de uso clásico para el patrón Singleton: quieres que todas las partes de tu programa compartan la misma configuración y estado del logger.
Organizarás tu código en dos archivos:
logger.go: Implementa tu logger Singleton seguro para hilos (thread-safe).Crea un struct
Loggercon dos campos:Prefix(string) para los prefijos de los mensajes de log, yMessageCount(int) para rastrear cuántos mensajes se han registrado.Usa variables a nivel de paquete con
sync.Oncepara asegurar que solo se cree una instancia. Implementa una funciónGetLogger()que inicialice el logger con un prefijo por defecto de"[LOG]"y devuelva la instancia singleton.Añade estos métodos a tu Logger:
SetPrefix(prefix string)- cambia el prefijo del loggerLog(message string) string- incrementa el contador de mensajes y devuelve una cadena formateada:[prefix] #[count]: [message]GetCount() int- devuelve el número total de mensajes registrados
main.go: Demuestra que múltiples llamadas aGetLogger()devuelven la misma instancia.Lee un nuevo valor de prefijo, luego lee un recuento de mensajes seguido de cada mensaje a registrar.
Obtén la instancia del logger, establece el prefijo personalizado, luego registra cada mensaje e imprime el resultado. Después de registrar todos los mensajes, obtén la instancia del logger de nuevo (para demostrar que es la misma) e imprime el recuento total de mensajes.
Se proporcionarán las siguientes entradas:
- Línea 1: Prefijo personalizado a establecer
- Línea 2: Número de mensajes
- Líneas siguientes: Cada mensaje a registrar
Por ejemplo, dado:
[APP]
3
Server started
User connected
Request processedTu salida debería ser:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3Y dado:
[DEBUG]
2
Initializing cache
Cache readyTu salida debería ser:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2Y dado:
[ERROR]
1
Connection failedTu salida debería ser:
[ERROR] #1: Connection failed
Total messages logged: 1La idea clave es que no importa cuántas veces llames a GetLogger(), siempre obtendrás la misma instancia con su estado compartido. ¡El recuento de mensajes persiste porque solo existe un Logger!
Hoja de referencia
El patrón Singleton garantiza que un tipo tenga una sola instancia en todo el programa y proporciona un punto de acceso global a ella. Esto es útil para recursos compartidos como gestores de configuración, conexiones a bases de datos o registradores (loggers).
Implementa Singleton utilizando una variable a nivel de paquete combinada con sync.Once para una inicialización segura para hilos (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
}El sync.Once garantiza que la función de inicialización se ejecute exactamente una vez, incluso cuando múltiples goroutines llaman a GetInstance() simultáneamente. Cada llamada posterior devuelve la misma instancia:
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 y cfg2 apuntan a la misma instanciaUsa Singleton con moderación, ya que introduce un estado global que puede dificultar las pruebas y ocultar dependencias. Considera la inyección de dependencias como alternativa.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lee el prefijo personalizado
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Lee el número de mensajes
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Lee cada mensaje
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: Obtén la instancia del logger usando GetLogger()
// TODO: Establece el prefijo personalizado usando SetPrefix()
// TODO: Registra cada mensaje e imprime el resultado
// TODO: Obtén la instancia del logger de nuevo (para demostrar que es la misma)
// y muestra el recuento total de mensajes en el formato: "Total messages logged: X"
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool