Menu
Coddy logo textTech

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

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

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

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

Шпаргалка

Чтобы создать по-настоящему приватные данные в 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>")
quiz iconПроверьте себя

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

Все уроки раздела Object Oriented Programming