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 tu programa y proporciona un punto de acceso global a ella. Esto resulta útil para recursos compartidos como administradores de configuración, conexiones de base de datos o loggers.
En Go, implementamos Singleton usando una variable a nivel de package combinada con sync.Once para garantizar una inicialización segura para los subprocesos:
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 garantiza que la función de inicialización se ejecute exactamente una vez, incluso cuando varias 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 práctico, introduce un estado global que puede dificultar las pruebas y ocultar dependencias. Considera si la inyección de dependencias podría ser una alternativa mejor para tu caso de uso.
Desafío
Fácil¡Construyamos un singleton de Logger que garantice que exista una sola instancia del logger en toda tu aplicación! Este es un caso de uso clásico del patrón Singleton. Quieres que todas las partes de tu programa compartan la misma configuración y el mismo estado del logger.
Organizarás tu código en dos archivos:
logger.go: Implementa tu logger Singleton seguro para subprocesos.Crea una estructura
Loggercon dos campos:Prefix(string) para los prefijos de los mensajes de registro yMessageCount(int) para llevar la cuenta de cuántos mensajes se han registrado.Usa variables a nivel de paquete con
sync.Oncepara garantizar que solo se cree una instancia. Implementa una funciónGetLogger()que inicialice el logger con un prefijo predeterminado 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 recuento de mensajes y devuelve una cadena con formato:[prefix] #[count]: [message]GetCount() int- devuelve el número total de mensajes registrados
main.go: Demuestra que varias llamadas aGetLogger()devuelven la misma instancia.Lee un nuevo valor de prefijo y después lee una cantidad de mensajes seguida de cada mensaje que se registrará.
Obtén la instancia del logger, establece el prefijo personalizado, después registra cada mensaje e imprime el resultado. Después de registrar todos los mensajes, obtén de nuevo la instancia del logger (para demostrar que es la misma) e imprime el recuento total de mensajes.
Se proporcionarán las siguientes entradas:
- Línea 1: Prefijo personalizado que se establecerá
- Línea 2: Número de mensajes
- Líneas siguientes: Cada mensaje que se registrará
Por ejemplo, dada la siguiente entrada:
[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 dada la siguiente entrada:
[DEBUG]
2
Initializing cache
Cache readyTu salida debería ser:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2Y dada la siguiente entrada:
[ERROR]
1
Connection failedTu salida debería ser:
[ERROR] #1: Connection failed
Total messages logged: 1La idea clave es que, sin importar cuántas veces llames a GetLogger(), siempre obtienes la misma instancia con su estado compartido. El recuento de mensajes persiste porque ¡solo existe un Logger!
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)
// and print the total message count in format: "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 PoolPractica por tu cuenta: Compilador de Go online