Menu
CoddyTech

用闭包实现隐私

本课属于 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 只是礼貌性建议不同,这种方法能让数据真正无法访问。

当然,如果你无法与隐藏的数据交互,它就没有用。在下一课中,你将学习如何在构造函数中定义方法,以读取和修改这些私有变量,从而在保持真正封装的同时实现受控访问。

challenge icon

挑战

简单

我们来创建一个 SecretKeeper 类,用闭包来演示真正的数据隐私!与上一课中的下划线命名约定不同,这种方法能让数据从对象外部真正无法访问。

你要把代码分到两个文件中:

  • SecretKeeper.lua:创建一个类,在构造函数中把秘密存储在局部变量里,而不是存储在 self 上。构造函数 :new(secretValue) 应该把秘密存储在一个只存在于闭包内的局部变量中。目前,对象还无法通过任何方式访问这个秘密(下一课会介绍如何访问!),但你还应该在实例上存储一个公开的 name 属性,并将其设为 "Keeper",这样我们就能验证对象是否正常工作。
  • main.lua:引入你的 SecretKeeper 模块,并从输入中读取一个秘密值。用这个秘密创建一个 SecretKeeper 实例。然后打印以下三项,演示这个秘密确实是私有的:
    • 保管者的 name 属性(应打印 Keeper)
    • 尝试访问 keeper.secret 时会发生什么(应为 nil)
    • 尝试访问 keeper._secret 时会发生什么(也应为 nil)

你会收到一个输入:

  1. 要存储的秘密值(例如 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>")
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Lua 在线编译器