0Pricing
Lua Academy · 课时

使用闭包实现封装

使用基于闭包的对象模式隐藏私有状态。

使用闭包实现封装 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 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.")

快速检查

与基于元表的对象相比,基于闭包的对象的主要优势是什么?

回顾:使用闭包实现封装

总结:

  • 闭包模式:工厂函数返回方法表;状态是上值
  • 真正私有:无法从外部访问上值
  • 所有返回的方法共享相同的私有上值
  • 权衡:每个实例占用更多内存,但隐私性更好
  • 适用于安全关键代码或函数式风格设计

常见问题解答

「使用闭包实现封装」课时是免费的吗?

是的 — 「使用闭包实现封装」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。

「使用闭包实现封装」这节课中我会学到什么?

使用基于闭包的对象模式隐藏私有状态。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Lua Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「使用闭包实现封装」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Lua Academy 课中编写并运行代码吗?

能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 通过元表实现类
  2. 构造函数和 new()
  3. 实例方法和 self
  4. 使用闭包实现封装
← 返回 Lua Academy