Таблица 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 гарантирует общее состояние во всем вашем приложении. Это идеально подходит для глобальных настроек, к которым различным частям вашего кода необходимо обращаться и которые нужно согласованно изменять.
Задание
ЛегкоДавайте создадим менеджер настроек игры, используя паттерн Одиночка (Singleton)! Вы создадите модуль конфигурации, который гарантирует, что каждая часть вашей программы обращается к одним и тем же настройкам — независимо от того, сколько раз он будет подключен.
Вы организуете свой код в двух файлах:
GameSettings.lua: Создайте модуль Singleton, который хранит конфигурацию игры. Ваша таблица настроек должна включатьmusicVolume(начальное значение 70),sfxVolume(начальное значение 100) иdifficulty(начальное значение"normal"). Добавьте три метода::setMusicVolume(value),:setSfxVolume(value)и:setDifficulty(level)для изменения этих настроек. Не забудьте вернуть таблицу в конце модуля!main.lua: Продемонстрируйте работу Singleton, подключив модуль GameSettings дважды в разные переменные. Используйте первую переменную, чтобы изменить громкость музыки и сложность. Затем используйте вторую переменную, чтобы вывести все три настройки — если Singleton работает правильно, обе переменные ссылаются на одну и ту же таблицу, поэтому изменения, внесенные через одну, будут видны через другую.
Вы получите два входных значения:
- Новая громкость музыки (число)
- Новый уровень сложности (строка, например
"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
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур