Таблицы только для чтения
Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 46 из 70.
Замыкания обеспечивают настоящую приватность, но они требуют определения методов внутри конструктора. Существует другой подход для иного случая использования: создание объекта только для чтения после его создания. Это полезно, когда вы хотите, чтобы поля были видимыми, но неизменяемыми — например, координаты фиксированной точки.
Метаметод __newindex перехватывает попытки присвоения новых значений таблице. Заставив его блокировать все операции присваивания, вы можете «заморозить» объект:
local ImmutablePoint = {}
function ImmutablePoint.new(x, y)
local data = {x = x, y = y}
local proxy = {}
setmetatable(proxy, {
__index = data,
__newindex = function()
error("Cannot modify immutable object")
end
})
return proxy
endХитрость заключается в использовании прокси-таблицы. Фактические данные находятся в data, в то время как пользователь взаимодействует с пустой proxy.
При чтении __index извлекает данные из data. При записи вместо этого срабатывает __newindex — и мы его блокируем:
local p = ImmutablePoint.new(10, 20)
print(p.x) -- 10 (чтение работает)
p.x = 50 -- Ошибка: Невозможно изменить неизменяемый объектЭтот паттерн полезен для объектов конфигурации, математических констант или любых данных, которые не должны меняться после инициализации. Объект остается полностью доступным для чтения — вы просто не можете его изменить.
Задание
ЛегкоДавайте создадим класс ImmutableConfig, который создает объекты конфигурации только для чтения! После создания эти объекты конфигурации должны позволять чтение своих настроек, но полностью блокировать любые попытки их изменения.
Вы организуете свой код в двух файлах:
ImmutableConfig.lua: Создайте модуль, который создает «замороженные» объекты конфигурации. Ваша функция.new(appName, version, maxUsers)должна сохранять эти три значения внутри, а затем возвращать прокси-таблицу, которая позволяет читать все поля, но предотвращает любые изменения. Когда кто-то пытается изменить значение, она должна выводитьError: Config is read-onlyвместо вызова фактической ошибки.main.lua: Оживите ваш ImmutableConfig! Прочитайте три входных значения для конфигурации, создайте объект конфигурации, а затем продемонстрируйте, что он работает правильно, выполнив следующие действия:- Вывод всех трех исходных значений
- Попытка изменить поле
maxUsers(что должно вызвать сообщение об ошибке) - Повторный вывод
maxUsers, чтобы доказать, что значение на самом деле не изменилось
Вы получите три входных значения:
- Название приложения (строка)
- Версия (строка)
- Максимальное количество пользователей (число)
Ваш вывод должен содержать значения конфигурации, ошибку при попытке изменения и подтверждение того, что значение осталось неизменным:
App: {appName}
Version: {version}
Max Users: {maxUsers}
Error: Config is read-only
Max Users: {maxUsers}Например, если входные данные — MyApp, 2.0 и 100, вывод должен быть следующим:
App: MyApp
Version: 2.0
Max Users: 100
Error: Config is read-only
Max Users: 100Помните: паттерн «прокси» использует пустую таблицу, с которой взаимодействует пользователь, в то время как фактические данные живут в отдельной внутренней таблице. Метаметод __index извлекает данные из таблицы данных, а __newindex перехватывает (и блокирует) любые попытки записи!
Шпаргалка
Метаметод __newindex перехватывает попытки присвоения новых значений таблице. Блокируя все присваивания, вы можете создавать неизменяемые (read-only) объекты:
local ImmutablePoint = {}
function ImmutablePoint.new(x, y)
local data = {x = x, y = y}
local proxy = {}
setmetatable(proxy, {
__index = data,
__newindex = function()
error("Cannot modify immutable object")
end
})
return proxy
endПаттерн «прокси» использует пустую таблицу (proxy), с которой взаимодействует пользователь, в то время как фактические данные хранятся в отдельной внутренней таблице (data):
__indexизвлекает значения из таблицыdataпри чтении__newindexперехватывает попытки записи и блокирует их
local p = ImmutablePoint.new(10, 20)
print(p.x) -- 10 (чтение работает)
p.x = 50 -- Ошибка: Cannot modify immutable objectЭтот паттерн полезен для объектов конфигурации, математических констант или любых данных, которые должны оставаться неизменными после инициализации.
Попробуйте сами
-- Импорт модуля ImmutableConfig
local ImmutableConfig = require('ImmutableConfig')
-- Чтение входных данных
local appName = io.read()
local version = io.read()
local maxUsers = tonumber(io.read())
-- TODO: Создать объект конфигурации с помощью ImmutableConfig.new()
-- TODO: Вывести все три исходных значения в формате:
-- App: {appName}
-- Version: {version}
-- Max Users: {maxUsers}
-- TODO: Попытаться изменить поле maxUsers (это должно вызвать сообщение об ошибке)
-- TODO: Снова вывести maxUsers, чтобы доказать, что значение не изменилось
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур