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Проверьте себя

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

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