Menu
Coddy logo textTech

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 instancia

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

challenge icon

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 Logger con dos campos: Prefix (string) para los prefijos de los mensajes de log, y MessageCount (int) para rastrear cuántos mensajes se han registrado.

    Usa variables a nivel de paquete con sync.Once para asegurar que solo se cree una instancia. Implementa una función GetLogger() 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 logger
    • Log(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 a GetLogger() 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 processed

Tu salida debería ser:

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

Y dado:

[DEBUG]
2
Initializing cache
Cache ready

Tu salida debería ser:

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

Y dado:

[ERROR]
1
Connection failed

Tu salida debería ser:

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

La 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 instancia

Usa 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"
}
quiz iconPonte a prueba

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