Замыкания для приватности
Часть раздела Объектно-ориентированное программирование путешествия по 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Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигурПотренируйтесь самостоятельно: Онлайн-компилятор Lua