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!

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