Menu
Coddy logo textTech

Таблица Singleton

Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 63 из 70.

Иногда вам нужен ровно один экземпляр чего-либо во всей программе — менеджер конфигурации, система логирования или настройки игры. Создание нескольких копий вызовет проблемы: в какой из них содержатся правильные данные? Паттерн Singleton гарантирует существование только одного экземпляра.

В Lua реализация Singleton удивительно проста благодаря тому, как работают модули. Когда вы вызываете require для модуля, Lua кэширует результат. Последующие вызовы require возвращают кэшированную таблицу, а не новую.

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

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

return GameConfig

Теперь в любом месте вашей программы:

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

config1:setVolume(50)
print(config2.volume)  -- 50 (та же самая таблица!)

И config1, и config2 ссылаются на одну и ту же таблицу. Изменения, внесенные через одну переменную, видны через другую. Это происходит автоматически, так как функция require в Lua сохраняет возвращаемое значение в package.loaded и использует его повторно.

В отличие от паттерна Factory, который создает новые объекты, Singleton гарантирует общее состояние во всем вашем приложении. Это идеально подходит для глобальных настроек, к которым различным частям вашего кода необходимо обращаться и которые нужно согласованно изменять.

challenge icon

Задание

Легко

Давайте создадим менеджер настроек игры, используя паттерн Одиночка (Singleton)! Вы создадите модуль конфигурации, который гарантирует, что каждая часть вашей программы обращается к одним и тем же настройкам — независимо от того, сколько раз он будет подключен.

Вы организуете свой код в двух файлах:

  • GameSettings.lua: Создайте модуль Singleton, который хранит конфигурацию игры. Ваша таблица настроек должна включать musicVolume (начальное значение 70), sfxVolume (начальное значение 100) и difficulty (начальное значение "normal"). Добавьте три метода: :setMusicVolume(value), :setSfxVolume(value) и :setDifficulty(level) для изменения этих настроек. Не забудьте вернуть таблицу в конце модуля!
  • main.lua: Продемонстрируйте работу Singleton, подключив модуль GameSettings дважды в разные переменные. Используйте первую переменную, чтобы изменить громкость музыки и сложность. Затем используйте вторую переменную, чтобы вывести все три настройки — если Singleton работает правильно, обе переменные ссылаются на одну и ту же таблицу, поэтому изменения, внесенные через одну, будут видны через другую.

Вы получите два входных значения:

  1. Новая громкость музыки (число)
  2. Новый уровень сложности (строка, например "easy" или "hard")

После изменения настроек через первую переменную выведите три настройки, используя вторую переменную, каждую на отдельной строке в следующем порядке: громкость музыки, громкость спецэффектов (sfx), затем сложность.

Например, если на вход поданы 50 и hard, вывод должен быть следующим:

50
100
hard

Если на вход поданы 25 и easy, вывод должен быть следующим:

25
100
easy

Обратите внимание, что громкость спецэффектов остается на значении по умолчанию 100, так как мы ее не меняли, но громкость музыки и сложность отражают изменения, сделанные через первую переменную, что доказывает: обе переменные указывают на один и тот же экземпляр Singleton!

Шпаргалка

Паттерн Одиночка (Singleton) гарантирует, что во всей вашей программе существует только один экземпляр объекта. Это полезно для менеджеров конфигурации, систем логирования или настроек игры, где наличие нескольких экземпляров привело бы к несогласованности данных.

В Lua паттерн Одиночка реализуется естественным образом через систему модулей. Когда вы вызываете require для модуля, Lua кэширует результат в package.loaded. Последующие вызовы require возвращают ту же самую кэшированную таблицу, а не новую.

Создание модуля-Одиночки:

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

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

return GameConfig

Использование Одиночки:

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

config1:setVolume(50)
print(config2.volume)  -- 50 (та же таблица!)

Обе переменные config1 и config2 ссылаются на одну и ту же таблицу. Изменения, внесенные через одну переменную, сразу же становятся видимыми через другую, что обеспечивает общее состояние во всем вашем приложении.

Паттерн Одиночка отличается от паттерна Фабрика: в то время как Фабрика создает новые объекты, Одиночка гарантирует наличие одного общего экземпляра с согласованным состоянием.

Попробуйте сами

-- main.lua - Демонстрация паттерна Одиночка (Singleton)

-- Чтение входных данных
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()

-- TODO: Подключите модуль GameSettings в переменную (например, settings1)

-- TODO: Подключите модуль GameSettings ПОВТОРНО в ДРУГУЮ переменную (например, settings2)
-- (Если Одиночка работает правильно, обе переменные ссылаются на одну и ту же таблицу!)

-- TODO: Используйте ПЕРВУЮ переменную, чтобы:
--   - Установить громкость музыки (music volume) в значение newMusicVolume
--   - Установить сложность (difficulty) в значение newDifficulty

-- TODO: Используйте ВТОРУЮ переменную для вывода настроек
-- Выведите каждое значение на новой строке: musicVolume, sfxVolume, difficulty
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming