Menu
Coddy logo textTech

Доступ через замыкания

Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 45 из 70.

Приватные локальные переменные бесполезны, если вы никогда не сможете с ними взаимодействовать. Решение состоит в том, чтобы определить методы геттеры и сеттеры внутри конструктора — там, где они имеют доступ к области видимости замыкания.

Поскольку эти методы создаются внутри :new(), они могут «видеть» локальные переменные. Вы назначаете их непосредственно объекту:

local Counter = {}
Counter.__index = Counter

function Counter:new()
    local count = 0  -- Приватная переменная
    
    local obj = {}
    
    -- Определение методов внутри конструктора
    function obj:getCount()
        return count
    end
    
    function obj:increment()
        count = count + 1
    end
    
    setmetatable(obj, Counter)
    return obj
end

Теперь к приватной переменной count можно получить доступ — но только через методы, которые вы предоставляете:

local c = Counter:new()
print(c:getCount())  -- 0
c:increment()
c:increment()
print(c:getCount())  -- 2
print(c.count)       -- nil (все еще скрыто!)

Обратите внимание, что :getCount() и :increment() определены непосредственно в obj, а не в таблице класса. Каждый экземпляр получает свою собственную копию этих функций, каждая из которых имеет доступ к своей собственной приватной count. В этом заключается компромисс: истинная приватность стоит немного больше памяти, так как методы не разделяются через прототип.

challenge icon

Задание

Легко

Давайте создадим класс Temperature, который использует приватность на основе замыканий для хранения значения температуры в градусах Цельсия, с методами геттерами и сеттерами, определенными внутри конструктора для обеспечения контролируемого доступа!

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

  • Temperature.lua: Создайте класс, в котором значение температуры хранится в локальной переменной внутри конструктора — полностью скрытой от внешнего доступа. Конструктор :new(initialCelsius) должен сохранять температуру в локальной переменной, а затем определять эти методы непосредственно на экземпляре объекта:
    • :getCelsius() — возвращает текущую температуру в градусах Цельсия
    • :setCelsius(value) — обновляет температуру до нового значения
    • :getFahrenheit() — возвращает температуру, преобразованную в градусы Фаренгейта по формуле: (celsius * 9/5) + 32
  • main.lua: Подключите ваш модуль Temperature и считайте два значения из входных данных: начальную температуру и новую температуру для установки. Создайте экземпляр Temperature с начальным значением, выведите показания в Цельсиях и Фаренгейтах, затем используйте сеттер для изменения температуры и снова выведите оба показания.

Вы получите два входных значения:

  1. Начальная температура в градусах Цельсия (число)
  2. Новая температура для установки (число)

Ваш вывод должен состоять из четырех строк, показывающих показания температуры до и после изменения:

Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}

Например, если входные данные 0 и 100, вывод должен быть следующим:

Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212

Помните: значение температуры должно храниться в локальной переменной внутри :new(), а не в self. Методы геттера и сеттера определяются внутри конструктора, где они могут получить доступ к этой приватной переменной через замыкание. Попытка прямого доступа к temp.celsius или temp._celsius должна возвращать nil!

Шпаргалка

Чтобы создать по-настоящему приватные переменные в классах Lua, определите методы геттеры и сеттеры внутри конструктора, где они имеют доступ к локальным переменным через замыкание.

Методы назначаются непосредственно экземпляру объекта (а не таблице класса), что дает каждому экземпляру собственную копию с доступом к его собственным приватным переменным:

local Counter = {}
Counter.__index = Counter

function Counter:new()
    local count = 0  -- Приватная переменная
    
    local obj = {}
    
    -- Определяем методы внутри конструктора
    function obj:getCount()
        return count
    end
    
    function obj:increment()
        count = count + 1
    end
    
    setmetatable(obj, Counter)
    return obj
end

Использование:

local c = Counter:new()
print(c:getCount())  -- 0
c:increment()
c:increment()
print(c:getCount())  -- 2
print(c.count)       -- nil (все еще скрыто!)

Компромисс: Каждый экземпляр получает собственную копию методов (не разделяемую через прототип), что потребляет больше памяти, но обеспечивает настоящую приватность.

Попробуйте сами

-- Подключить модуль Temperature
local Temperature = require('Temperature')

-- Прочитать входные значения
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())

-- TODO: Создать экземпляр Temperature с начальным значением

-- TODO: Вывести начальные показания в градусах Цельсия и Фаренгейта

-- TODO: Использовать сеттер для изменения температуры на newTemp

-- TODO: Вывести обновленные показания в градусах Цельсия и Фаренгейта
quiz iconПроверьте себя

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

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