0Pricing
Lua Academy · Урок

Инкапсуляция с замыканиями

Скрывайте внутреннее состояние с помощью объектных шаблонов на основе замыканий.

«Инкапсуляция с замыканиями» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Классы с помощью метатаблиц
  2. Конструкторы и new()
  3. Методы экземпляров и self
  4. Инкапсуляция с замыканиями
← Назад к Lua Academy