Инкапсуляция с замыканиями
Скрывайте внутреннее состояние с помощью объектных шаблонов на основе замыканий.
«Инкапсуляция с замыканиями» — бесплатный урок Lua Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Lua Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Lua Academy содержит 4 уроков всего.
Объекты на основе замыканий
Альтернатива ООП на метатаблицах: используйте замыкание, чтобы скрыть состояние. Объект представляет собой таблицу функций; все функции замыкают приватные переменные. Метатаблица не нужна. Это действительно приватное состояние, недоступное извне.
local function makeCounter(start)
local count = start or 0 -- truly private
return {
increment = function() count=count+1 end,
decrement = function() count=count-1 end,
get = function() return count end,
reset = function() count=0 end,
}
end
local c = makeCounter(10)
c.increment(); c.increment()
print(c.get()) -- 12
print(c.count) -- nil (truly private!)Приватное и публичное
В замыканиях различие между приватным и публичным абсолютное: замыкания совместно используют внешнюю переменную (приватную), а внешний код видит только возвращённую таблицу (публичную). Получить доступ к приватным переменным можно только через публичный API.
local function makeAccount(initial)
local balance = initial
local transactions = {}
local function record(type, amount)
transactions[#transactions+1] = {type=type,amount=amount}
end
return {
deposit = function(n) record("D",n); balance=balance+n end,
withdraw = function(n)
if n>balance then error("insufficient funds") end
record("W",n); balance=balance-n
end,
balance = function() return balance end,
history = function() return transactions end,
}
endОбщие замыкания
Несколько функций в одной возвращаемой таблице используют одни и те же внешние переменные. Так состояние разделяется между методами без публичного доступа к нему. Все методы видят одни и те же приватные переменные.
local function makeQueue()
local items = {} -- shared by all methods
local size = 0
return {
enqueue = function(v) size=size+1; items[size]=v end,
dequeue = function()
if size==0 then return nil end
local head=1
while items[head]==nil do head=head+1 end
local v=items[head]; items[head]=nil; return v
end,
isEmpty = function() return size==0 end,
count = function() return size end,
}
endЗамыкания и метатаблицы
Замыкания: действительно приватное состояние, отсутствие накладных расходов метатаблиц, но у каждого экземпляра собственные копии функций (больше памяти). Метатаблицы: общие методы (меньше памяти), возможность проверки с помощью отладочных инструментов и поддержка наследования. Выбирайте подход с учётом требований к приватности и количества экземпляров.
-- Closure: per-instance functions (more memory)
local function makePoint(x,y)
return {
x = function() return x end,
y = function() return y end,
distance = function()
return math.sqrt(x*x+y*y)
end
}
end
-- Metatable: shared methods (less memory)
-- local Point = {}; Point.__index = Point
-- function Point.new(x,y) return setmetatable({x=x,y=y},Point) endИзменяемые замыкания
Замыкания над изменяемыми переменными позволяют создавать объекты с состоянием вообще без таблиц. Одно замыкание может хранить состояние и вычислять результаты.
local function makeAccumulator()
local sum, count = 0, 0
return {
add = function(n) sum=sum+n; count=count+1 end,
mean = function()
return count>0 and sum/count or 0
end,
total = function() return sum end,
n = function() return count end,
}
end
local acc = makeAccumulator()
for _, v in ipairs({10,20,30,40,50}) do
acc.add(v)
end
print(acc.total(),acc.mean(),acc.n()) -- 150 30.0 5Конструктор с проверкой
Конструкторы на основе замыканий выполняют проверку в фабричной функции, а не в методе. При недопустимых входных данных фабрика возвращает nil или ошибку ещё до создания объекта.
local function makeRange(lo, hi)
assert(lo<=hi,"lo must be <= hi")
return {
contains = function(v) return v>=lo and v<=hi end,
clamp = function(v) return math.max(lo,math.min(hi,v)) end,
lo = function() return lo end,
hi = function() return hi end,
span = function() return hi-lo end,
}
end
local r = makeRange(0,100)
print(r.contains(50)) -- true
print(r.clamp(150)) -- 100События с замыканиями
Издатель событий, реализованный с помощью замыканий: список обработчиков приватный, а add/remove/emit — публичный API.
local function makeEmitter()
local handlers = {}
return {
on = function(event, fn)
handlers[event] = handlers[event] or {}
handlers[event][#handlers[event]+1] = fn
end,
emit = function(event, ...)
for _, fn in ipairs(handlers[event] or {}) do
fn(...)
end
end,
off = function(event) handlers[event] = {} end,
}
end
local emitter = makeEmitter()
emitter.on("data", function(v) print("Got:", v) end)
emitter.emit("data", 42) -- Got: 42Мемоизация с помощью замыканий
Обёртка для мемоизации на основе замыканий: кэш и обёрнутая функция приватны, возвращается только мемоизированная версия.
local function memoize(fn)
local cache = {}
return function(...)
local key = table.concat({...}, ",")
if cache[key] == nil then
cache[key] = fn(...)
end
return cache[key]
end
end
local expensiveFib
expensiveFib = memoize(function(n)
if n<=1 then return n end
return expensiveFib(n-1)+expensiveFib(n-2)
end)
for i=0,10 do io.write(expensiveFib(i).." ") end
print()Расширение объектов на основе замыканий
Объект на основе замыкания можно расширить, создав новый объект, который вызывает методы исходного. Это похоже на обёртку, а не на наследование: вызовы делегируются исходному объекту.
local function makeTimedCounter(start)
local base = makeCounter and makeCounter(start) or (function()
local c=start or 0
return {inc=function()c=c+1 end,get=function()return c end}
end)()
local callCount = 0
return {
increment = function()
callCount=callCount+1
base.inc()
end,
get = base.get,
calls = function() return callCount end,
}
endКогда использовать замыкания
Используйте ООП на основе замыканий, когда вам нужно действительно приватное и защищённое состояние, когда экземпляров мало (расход памяти несущественен) или когда вы пишете код в функциональном стиле. Используйте метатаблицы, когда нужны наследование, большое количество экземпляров или интеграция с библиотеками ООП.
-- Guideline summary:
-- Closures: true privacy, no metatable, self-contained
-- Metatables: shared methods, inheritance, instanceof checks
-- Rule of thumb:
-- < 100 instances with private state -> closures
-- > 100 instances OR need inheritance -> metatables
print("Both patterns are valid Lua OOP.")Быстрая проверка
Каково главное преимущество объектов на основе замыканий перед объектами на основе метатаблиц?
Итоги: замыкания для инкапсуляции
Кратко:
- Шаблон замыкания: фабричная функция возвращает таблицу методов, а состояние хранится во внешней переменной
- Действительно приватное состояние: извне невозможно получить доступ к внешним переменным
- Все возвращённые методы используют одни и те же приватные внешние переменные
- Компромисс: больше памяти на экземпляр, но лучшая приватность
- Используйте этот подход для критичного к безопасности кода или разработок в функциональном стиле
Часто задаваемые вопросы
Урок «Инкапсуляция с замыканиями» бесплатный?
Да — полный текст урока «Инкапсуляция с замыканиями» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Lua Academy, подпишись на CoddyKit PRO. Курс Lua Academy содержит 4 уроков всего.
Чему я научусь в уроке «Инкапсуляция с замыканиями»?
Скрывайте внутреннее состояние с помощью объектных шаблонов на основе замыканий. Ты практикуешь Lua Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Lua Academy?
Предыдущий опыт не требуется. Lua Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Инкапсуляция с замыканиями»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Lua Academy?
Да. Каждый урок Lua Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Классы с помощью метатаблиц
- Конструкторы и new()
- Методы экземпляров и self
- Инкапсуляция с замыканиями