Таблица Singleton
Часть раздела Объектно-ориентированное программирование путешествия по 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 ссылаются на одну и ту же таблицу. Изменения, внесенные через одну переменную, видны через другую. Это происходит автоматически, потому что функция Lua require сохраняет возвращаемое значение в 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!
Попробуйте сами
-- main.lua - Демонстрация паттерна Singleton
-- Чтение входных данных
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()
-- TODO: Подключите модуль GameSettings в переменную (например, settings1)
-- TODO: Подключите модуль GameSettings ПОВТОРНО в ДРУГУЮ переменную (например, settings2)
-- (Если Singleton работает правильно, обе переменные будут ссылаться на одну и ту же таблицу!)
-- TODO: Используйте ПЕРВУЮ переменную, чтобы:
-- - Установить громкость музыки (music volume) в значение newMusicVolume
-- - Установить сложность (difficulty) в значение newDifficulty
-- TODO: Используйте ВТОРУЮ переменную для вывода настроек
-- Выведите каждое значение на новой строке: musicVolume, sfxVolume, difficulty
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур