Доступ через замыкания
Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 45 из 70.
Приватные локальные переменные бесполезны, если вы никогда не сможете с ними взаимодействовать. Решение состоит в том, чтобы определить методы геттеры и сеттеры внутри конструктора — там, где они имеют доступ к области видимости замыкания.
Поскольку эти методы создаются внутри :new(), они могут «видеть» локальные переменные. Вы назначаете их непосредственно объекту:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Приватная переменная
local obj = {}
-- Определение методов внутри конструктора
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endТеперь к приватной переменной count можно получить доступ — но только через методы, которые вы предоставляете:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (все еще скрыто!)Обратите внимание, что :getCount() и :increment() определены непосредственно в obj, а не в таблице класса. Каждый экземпляр получает свою собственную копию этих функций, каждая из которых имеет доступ к своей собственной приватной count. В этом заключается компромисс: истинная приватность стоит немного больше памяти, так как методы не разделяются через прототип.
Задание
ЛегкоДавайте создадим класс Temperature, который использует приватность на основе замыканий для хранения значения температуры в градусах Цельсия, с методами геттерами и сеттерами, определенными внутри конструктора для обеспечения контролируемого доступа!
Вы организуете свой код в двух файлах:
Temperature.lua: Создайте класс, в котором значение температуры хранится в локальной переменной внутри конструктора — полностью скрытой от внешнего доступа. Конструктор:new(initialCelsius)должен сохранять температуру в локальной переменной, а затем определять эти методы непосредственно на экземпляре объекта::getCelsius()— возвращает текущую температуру в градусах Цельсия:setCelsius(value)— обновляет температуру до нового значения:getFahrenheit()— возвращает температуру, преобразованную в градусы Фаренгейта по формуле:(celsius * 9/5) + 32
main.lua: Подключите ваш модуль Temperature и считайте два значения из входных данных: начальную температуру и новую температуру для установки. Создайте экземпляр Temperature с начальным значением, выведите показания в Цельсиях и Фаренгейтах, затем используйте сеттер для изменения температуры и снова выведите оба показания.
Вы получите два входных значения:
- Начальная температура в градусах Цельсия (число)
- Новая температура для установки (число)
Ваш вывод должен состоять из четырех строк, показывающих показания температуры до и после изменения:
Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}Например, если входные данные 0 и 100, вывод должен быть следующим:
Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212Помните: значение температуры должно храниться в локальной переменной внутри :new(), а не в self. Методы геттера и сеттера определяются внутри конструктора, где они могут получить доступ к этой приватной переменной через замыкание. Попытка прямого доступа к temp.celsius или temp._celsius должна возвращать nil!
Шпаргалка
Чтобы создать по-настоящему приватные переменные в классах Lua, определите методы геттеры и сеттеры внутри конструктора, где они имеют доступ к локальным переменным через замыкание.
Методы назначаются непосредственно экземпляру объекта (а не таблице класса), что дает каждому экземпляру собственную копию с доступом к его собственным приватным переменным:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Приватная переменная
local obj = {}
-- Определяем методы внутри конструктора
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endИспользование:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (все еще скрыто!)Компромисс: Каждый экземпляр получает собственную копию методов (не разделяемую через прототип), что потребляет больше памяти, но обеспечивает настоящую приватность.
Попробуйте сами
-- Подключить модуль Temperature
local Temperature = require('Temperature')
-- Прочитать входные значения
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())
-- TODO: Создать экземпляр Temperature с начальным значением
-- TODO: Вывести начальные показания в градусах Цельсия и Фаренгейта
-- TODO: Использовать сеттер для изменения температуры на newTemp
-- TODO: Вывести обновленные показания в градусах Цельсия и Фаренгейта
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур