Замыкания для приватности
Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy — урок 44 из 70.
Соглашения об именовании указывают на намерения, но на самом деле они не предотвращают доступ. Кто угодно всё ещё может написать player._health = 999, и это сработает. Для настоящей приватности Lua предлагает другой подход: хранение данных в локальных переменных внутри конструктора вместо self.
Ключевая идея заключается в том, что локальные переменные внутри функции доступны только внутри этой функции — и любых функций, определенных внутри нее. Это называется замыканием:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- По-настоящему приватная! Не в self.
local obj = {}
setmetatable(obj, Counter)
return obj
endВ этом примере count существует только внутри функции :new(). Она не хранится в obj, поэтому нет способа получить к ней доступ извне — myCounter.count возвращает nil, и нет поля с префиксом в виде нижнего подчеркивания, которое можно было бы использовать для обхода.
Переменная count находится в области видимости замыкания. Она остается полностью скрытой от внешнего кода. (В текущем виде ничто еще не захватывает count; в следующем уроке вы добавите методы внутрь конструктора, которые будут замыкаться на ней, поэтому она будет существовать до тех пор, пока существует объект.) В отличие от self._count, что является лишь вежливым соглашением, этот подход делает данные по-настоящему недоступными.
Конечно, скрытые данные бесполезны, если вы никогда не сможете с ними взаимодействовать. В следующем уроке вы узнаете, как определять методы внутри конструктора, которые могут считывать и изменять эти приватные переменные, обеспечивая контролируемый доступ при сохранении истинной инкапсуляции.
Задание
ЛегкоДавайте создадим класс SecretKeeper, который демонстрирует настоящую приватность данных с использованием замыканий! В отличие от соглашения с нижним подчеркиванием из предыдущего урока, этот подход делает данные действительно недоступными извне объекта.
Вы организуете свой код в двух файлах:
SecretKeeper.lua: Создайте класс, в котором секрет хранится в локальной переменной внутри конструктора, а не вself. Конструктор:new(secretValue)должен сохранять секрет в локальной переменной, которая существует только внутри замыкания. На данный момент у объекта не будет способа получить доступ к этому секрету (это будет в следующем уроке!), но вы также должны сохранить публичное полеnameсо значением"Keeper"в экземпляре, чтобы мы могли убедиться, что объект работает.main.lua: Подключите модуль SecretKeeper и прочитайте секретное значение из ввода. Создайте экземпляр SecretKeeper с этим секретом. Затем продемонстрируйте, что секрет действительно приватный, выведя три вещи:- Поле
nameхранителя (должно вывестиKeeper) - Что происходит при попытке доступа к
keeper.secret(должно бытьnil) - Что происходит при попытке доступа к
keeper._secret(также должно бытьnil)
- Поле
Вы получите один входной параметр:
- Секретное значение для хранения (например,
TopSecret123)
Ваш вывод должен состоять из трех строк, показывающих, что хотя публичное поле работает, секрет полностью скрыт:
Name: Keeper
secret: nil
_secret: nilВывод будет одинаковым независимо от того, какой секрет вы передадите — потому что к нему нет доступа! Локальная переменная внутри конструктора невидима для внешнего мира. Это доказывает, что приватность на основе замыканий фундаментально отличается от соглашения об именовании с нижним подчеркиванием.
Например, если на вход подается MyPassword, вывод все равно должен быть:
Name: Keeper
secret: nil
_secret: nilПопробуйте сами
-- Подключите модуль SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- Прочитайте секретное значение из ввода
local secretValue = io.read()
-- TODO: Создайте экземпляр SecretKeeper с секретным значением
-- TODO: Выведите поле name объекта keeper (формат: "Name: <name>")
-- TODO: Выведите, что происходит при обращении к keeper.secret (формат: "secret: <value>")
-- TODO: Выведите, что происходит при обращении к keeper._secret (формат: "_secret: <value>")
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур