Замыкания для приватности
Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 44 из 70.
Соглашения об именовании обозначают назначение, но на самом деле не предотвращают доступ. Любой всё ещё может написать player._health = 999, и это сработает. Для настоящей privacy Lua предлагает другой подход: хранить данные в переменных local внутри конструктора, а не в self.
Ключевая идея заключается в том, что переменные local внутри function доступны только внутри этой function и любых функций, определённых внутри неё. Это называется closure:
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 находится в области видимости closure. Она полностью скрыта от внешнего кода. (В текущем виде пока ничто не сохраняет count; в следующем уроке ты добавишь внутри конструктора методы, которые замкнут в себе эту переменную, поэтому она будет сохраняться столько же, сколько существует объект.) В отличие от self._count, что является лишь вежливым предложением, этот подход делает данные действительно недоступными.
Конечно, скрытые данные бесполезны, если вы никогда не можете с ними взаимодействовать. В следующем уроке вы узнаете, как определять методы внутри конструктора, которые могут читать и изменять эти private переменные, предоставляя вам контролируемый доступ и сохраняя truly инкапсуляцию.
Задание
ЛегкоДавайте создадим класс SecretKeeper, демонстрирующий настоящую конфиденциальность data с помощью closures! В отличие от соглашения с подчёркиванием из предыдущего урока, этот подход делает data действительно недоступными извне объекта.
Вы организуете свой код в двух файлах:
SecretKeeper.lua: Create class, в которой secret хранится в переменной local внутри конструктора, а не вself. Конструктор:new(secretValue)должен сохранить secret в переменной local, существующей только внутри closure. Пока у объекта не будет способа accessing этот secret (это появится в следующем уроке!), но вы также должны сохранить public полеnameсо значением"Keeper"в instance, чтобы мы могли проверить, что объект работает.main.lua: Подключите модуль SecretKeeper с помощью require и прочитайте secret из input. Create instance SecretKeeper с этим secret. Затем продемонстрируйте, что secret truly private, выведя три значения:- поле
nameобъекта keeper (должно вывестиKeeper) - что happens при попытке accessing
keeper.secret(результатом должно бытьnil) - что happens при попытке accessing
keeper._secret(результатом также должно бытьnil)
- поле
Вы получите одно входное значение:
- значение secret для сохранения (например,
TopSecret123)
Ваш output должен состоять из трёх строк, показывающих, что public поле работает, а secret полностью скрыт:
Name: Keeper
secret: nil
_secret: nilOutput будет одинаковым независимо от того, какой secret вы передадите, поскольку нет способа accessing его! Переменная local внутри конструктора невидима для внешнего мира. Это доказывает, что privacy на основе closure принципиально отличается от соглашения об именовании с подчёркиванием.
Например, если input — MyPassword, output всё равно должен быть таким:
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