Menu
Coddy logo textTech

Tabla Singleton

Parte de la sección Object Oriented Programming del Journey de Lua de Coddy — lección 63 de 70.

A veces necesitas exactamente una instancia de algo en todo tu programa: un gestor de configuración, un sistema de registro o los ajustes del juego. Crear múltiples copias causaría problemas: ¿cuál tiene los datos correctos? El patrón Singleton asegura que solo exista una instancia.

En Lua, implementar un Singleton es sorprendentemente sencillo gracias a cómo funcionan los módulos. Cuando haces require de un módulo, Lua almacena en caché el resultado. Las llamadas posteriores a require devuelven la tabla almacenada en caché, no una nueva.

-- GameConfig.lua
local GameConfig = {
    volume = 80,
    difficulty = "normal"
}

function GameConfig:setVolume(v)
    self.volume = v
end

return GameConfig

Ahora, en cualquier parte de tu programa:

local config1 = require("GameConfig")
local config2 = require("GameConfig")

config1:setVolume(50)
print(config2.volume)  -- 50 (¡la misma tabla!)

Tanto config1 como config2 hacen referencia exactamente a la misma tabla. Los cambios realizados a través de una variable son visibles a través de la otra. Esto sucede automáticamente porque el require de Lua almacena el valor devuelto en package.loaded y lo reutiliza.

A diferencia del patrón Factory que crea nuevos objetos, un Singleton garantiza un estado compartido en toda su aplicación. Esto es ideal para configuraciones globales a las que múltiples partes de su código necesitan acceder y modificar de manera consistente.

challenge icon

Desafío

Fácil

¡Vamos a construir un gestor de ajustes de juego utilizando el patrón Singleton! Crearás un módulo de configuración que garantiza que cada parte de tu programa acceda exactamente a los mismos ajustes, sin importar cuántas veces sea requerido.

Organizarás tu código en dos archivos:

  • GameSettings.lua: Crea un módulo Singleton que almacene la configuración del juego. Tu tabla de ajustes debe incluir musicVolume (empezando en 70), sfxVolume (empezando en 100) y difficulty (empezando en "normal"). Añade tres métodos: :setMusicVolume(value), :setSfxVolume(value) y :setDifficulty(level) para modificar estos ajustes. ¡Recuerda devolver la tabla al final del módulo!
  • main.lua: Demuestra que el Singleton funciona requiriendo tu módulo GameSettings dos veces en variables diferentes. Usa la primera variable para cambiar el volumen de la música y la dificultad. Luego usa la segunda variable para imprimir los tres ajustes; si el Singleton funciona correctamente, ambas variables hacen referencia a la misma tabla, por lo que los cambios realizados a través de una son visibles a través de la otra.

Recibirás dos entradas:

  1. Nuevo volumen de música (un número)
  2. Nuevo nivel de dificultad (una cadena como "easy" o "hard")

Después de modificar los ajustes a través de la primera variable, imprime los tres ajustes usando la segunda variable, cada uno en su propia línea en este orden: volumen de música, volumen de sfx y luego la dificultad.

Por ejemplo, si las entradas son 50 y hard, la salida debería ser:

50
100
hard

Si las entradas son 25 y easy, la salida debería ser:

25
100
easy

Observa que el volumen de sfx permanece en su valor predeterminado de 100 ya que nunca lo cambiamos, pero el volumen de la música y la dificultad reflejan los cambios realizados a través de la primera variable, ¡lo que demuestra que ambas variables apuntan a la misma instancia Singleton!

Hoja de referencia

El patrón Singleton garantiza que solo exista una instancia de un objeto en todo el programa. Esto es útil para gestores de configuración, sistemas de registro (logging) o ajustes de juego donde múltiples instancias causarían inconsistencia de datos.

En Lua, el patrón Singleton se implementa de forma natural a través del sistema de módulos. Cuando usas require para un módulo, Lua guarda el resultado en caché en package.loaded. Las llamadas posteriores a require devuelven la misma tabla almacenada en caché, no una nueva.

Creación de un módulo Singleton:

-- GameConfig.lua
local GameConfig = {
    volume = 80,
    difficulty = "normal"
}

function GameConfig:setVolume(v)
    self.volume = v
end

return GameConfig

Uso del Singleton:

local config1 = require("GameConfig")
local config2 = require("GameConfig")

config1:setVolume(50)
print(config2.volume)  -- 50 (¡la misma tabla!)

Tanto config1 como config2 hacen referencia exactamente a la misma tabla. Los cambios realizados a través de una variable son visibles inmediatamente a través de la otra, lo que garantiza un estado compartido en toda la aplicación.

El patrón Singleton se diferencia del patrón Factory: mientras que Factory crea nuevos objetos, Singleton garantiza una única instancia compartida con un estado consistente.

Pruébalo tú mismo

-- main.lua - Demostrar el patrón Singleton

-- Leer entradas
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()

-- TODO: Requerir el módulo GameSettings en una variable (por ejemplo, settings1)

-- TODO: Requerir el módulo GameSettings OTRA VEZ en una variable DIFERENTE (por ejemplo, settings2)
-- (¡Si el Singleton funciona correctamente, ambas variables hacen referencia a la misma tabla!)

-- TODO: Usar la PRIMERA variable para:
--   - Establecer el volumen de la música a newMusicVolume
--   - Establecer la dificultad a newDifficulty

-- TODO: Usar la SEGUNDA variable para imprimir la configuración
-- Imprimir cada uno en su propia línea: musicVolume, sfxVolume, difficulty
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 Object Oriented Programming