Menu
Coddy logo textTech

Замыкания для приватности

Часть раздела Объектно-ориентированное программирование путешествия по 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, что является лишь вежливым соглашением, этот подход делает данные по-настоящему недоступными.

Конечно, скрытые данные бесполезны, если вы никогда не сможете с ними взаимодействовать. В следующем уроке вы узнаете, как определять методы внутри конструктора, которые могут считывать и изменять эти приватные переменные, обеспечивая контролируемый доступ при сохранении истинной инкапсуляции.

challenge icon

Задание

Легко

Давайте создадим класс SecretKeeper, который демонстрирует настоящую приватность данных с использованием замыканий! В отличие от соглашения с нижним подчеркиванием из предыдущего урока, этот подход делает данные действительно недоступными извне объекта.

Вы организуете свой код в двух файлах:

  • SecretKeeper.lua: Создайте класс, в котором секрет хранится в локальной переменной внутри конструктора, а не в self. Конструктор :new(secretValue) должен сохранять секрет в локальной переменной, которая существует только внутри замыкания. На данный момент у объекта не будет способа получить доступ к этому секрету (это будет в следующем уроке!), но вы также должны сохранить публичное поле name со значением "Keeper" в экземпляре, чтобы мы могли убедиться, что объект работает.
  • main.lua: Подключите модуль SecretKeeper и прочитайте секретное значение из ввода. Создайте экземпляр SecretKeeper с этим секретом. Затем продемонстрируйте, что секрет действительно приватный, выведя три вещи:
    • Поле name хранителя (должно вывести Keeper)
    • Что происходит при попытке доступа к keeper.secret (должно быть nil)
    • Что происходит при попытке доступа к keeper._secret (также должно быть nil)

Вы получите один входной параметр:

  1. Секретное значение для хранения (например, 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>")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua