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

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

challenge icon

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

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

Tu salida debería ser:

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

Y dada la siguiente entrada:

[DEBUG]
2
Initializing cache
Cache ready

Tu salida debería ser:

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

Y dada la siguiente entrada:

[ERROR]
1
Connection failed

Tu salida debería ser:

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

La 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"
}
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

Practica por tu cuenta: Compilador de Go online