Замыкания для приватности
Часть раздела Object Oriented Programming путешествия по 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 находится в области видимости замыкания. Она сохраняется до тех пор, пока существует объект, но она полностью скрыта от внешнего кода. В отличие от 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Шпаргалка
Чтобы создать по-настоящему приватные данные в 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. Нет никакого способа получить к ней доступ извне — в отличие от self._count (что является лишь соглашением об именовании), этот подход делает данные действительно недоступными.
Переменная сохраняется до тех пор, пока существует объект, но остается полностью скрытой от внешнего кода, обеспечивая истинную инкапсуляцию.
Попробуйте сами
-- Подключить модуль SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- Прочитать секретное значение из ввода
local secretValue = io.read()
-- TODO: Создать экземпляр SecretKeeper с секретным значением
-- TODO: Вывести поле name хранителя (формат: "Name: <name>")
-- TODO: Вывести, что происходит при обращении к keeper.secret (формат: "secret: <value>")
-- TODO: Вывести, что происходит при обращении к keeper._secret (формат: "_secret: <value>")
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур