使用闭包实现封装
使用基于闭包的对象模式隐藏私有状态。
使用闭包实现封装 是 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 反馈 — 无需本地设置。
此课程中的所有课时
- 通过元表实现类
- 构造函数和 new()
- 实例方法和 self
- 使用闭包实现封装