用闭包实现隐私
本课属于 Coddy Lua 学习旅程的“面向对象编程”单元。第 44 课,共 70 课。
命名约定可以表明意图,但实际上并不能阻止访问。任何人仍然可以写 player._health = 999,而且代码可以正常运行。要实现真正的隐私,Lua 提供了另一种方法:在构造函数中将数据存储在局部变量里,而不是存储在 self 上。
关键在于,函数内的局部变量只能在该函数及其内部定义的函数中访问。这称为闭包:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- 真正的私有变量!不在 self 上。
local obj = {}
setmetatable(obj, Counter)
return obj
end在这个示例中,count 只存在于 :new() 函数内部。它没有存储在 obj 中,因此无法从外部访问:myCounter.count 会返回 nil,也没有以下划线开头的字段可以绕过这种限制。
变量 count 位于闭包的作用域中。它完全对外部代码隐藏。(按照当前的写法,还没有任何内容捕获 count;在下一课中,你会在构造函数内添加闭包捕获它的方法,这样只要对象存在,它就会一直保留。)与 self._count 只是礼貌性建议不同,这种方法能让数据真正无法访问。
当然,如果你无法与隐藏的数据交互,它就没有用。在下一课中,你将学习如何在构造函数中定义方法,以读取和修改这些私有变量,从而在保持真正封装的同时实现受控访问。
挑战
简单我们来创建一个 SecretKeeper 类,用闭包来演示真正的数据隐私!与上一课中的下划线命名约定不同,这种方法能让数据从对象外部真正无法访问。
你要把代码分到两个文件中:
SecretKeeper.lua:创建一个类,在构造函数中把秘密存储在局部变量里,而不是存储在self上。构造函数:new(secretValue)应该把秘密存储在一个只存在于闭包内的局部变量中。目前,对象还无法通过任何方式访问这个秘密(下一课会介绍如何访问!),但你还应该在实例上存储一个公开的name属性,并将其设为"Keeper",这样我们就能验证对象是否正常工作。main.lua:引入你的 SecretKeeper 模块,并从输入中读取一个秘密值。用这个秘密创建一个 SecretKeeper 实例。然后打印以下三项,演示这个秘密确实是私有的:- 保管者的
name属性(应打印Keeper) - 尝试访问
keeper.secret时会发生什么(应为nil) - 尝试访问
keeper._secret时会发生什么(也应为nil)
- 保管者的
你会收到一个输入:
- 要存储的秘密值(例如
TopSecret123)
你的输出应包含三行,说明公开属性可以正常使用,而秘密则被完全隐藏:
Name: Keeper
secret: nil
_secret: nil无论你传入什么秘密,输出都会相同,因为没有办法访问这个秘密!构造函数中的局部变量对外部不可见。这证明了基于闭包的隐私与下划线命名约定有本质区别。
例如,如果输入是 MyPassword,输出仍应为:
Name: Keeper
secret: nil
_secret: nil亲手试试
-- 加载 SecretKeeper 模块
local SecretKeeper = require('SecretKeeper')
-- 从输入中读取秘密值
local secretValue = io.read()
-- TODO:用秘密值创建一个 SecretKeeper 实例
-- TODO:打印 keeper 的 name 字段(格式:"Name: <name>")
-- TODO:打印访问 keeper.secret 时的结果(格式:"secret: <value>")
-- TODO:打印访问 keeper._secret 时的结果(格式:"_secret: <value>")
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Lua 在线编译器