单例表
本课属于 Coddy Lua 学习旅程的“面向对象编程”单元。第 63 课,共 70 课。
有时,你需要在整个程序中只创建某个对象的一个实例,比如配置管理器、日志系统或游戏设置。创建多个副本会带来问题:哪个副本的数据才是正确的?单例模式可确保只存在一个实例。
在 Lua 中,由于模块的工作方式,实现单例出奇地简单。当你 require 一个模块时,Lua 会缓存结果。后续调用 require 会返回缓存的表,而不是一个新表。
-- GameConfig.lua
local GameConfig = {
volume = 80,
difficulty = "normal"
}
function GameConfig:setVolume(v)
self.volume = v
end
return GameConfig
现在,在程序中的任何位置:
local config1 = require("GameConfig")
local config2 = require("GameConfig")
config1:setVolume(50)
print(config2.volume) -- 50(是同一个表!)
config1 和 config2 引用的是同一个表。通过一个变量所做的更改也能通过另一个变量看到。这会自动发生,因为 Lua 的 require 会将返回的值存储在 package.loaded 中并重复使用。
与会创建新对象的工厂模式不同,单例模式可以确保整个应用中的状态共享。这非常适合全局设置,代码中的多个部分都需要以一致的方式访问和修改这些设置。
挑战
简单让我们使用单例模式来构建一个游戏设置管理器!你将创建一个配置模块,确保程序的每个部分访问的都是完全相同的设置,无论该模块被导入多少次。
你将把代码放在两个文件中:
GameSettings.lua:创建一个保存游戏配置的单例模块。设置表应包含musicVolume(初始值为 70)、sfxVolume(初始值为 100)和difficulty(初始值为"normal")。添加三个方法::setMusicVolume(value)、:setSfxVolume(value)和:setDifficulty(level),用于修改这些设置。记得在模块末尾返回这个表!main.lua:将 GameSettings 模块导入两次,并分别存入不同的变量,以证明单例模式有效。使用第一个变量更改音乐音量和难度。然后使用第二个变量打印全部三项设置。如果单例模式正常工作,两个变量将引用同一个表,因此通过其中一个变量所做的更改也能通过另一个变量看到。
你将收到两个输入:
- 新的音乐音量(一个数字)
- 新的难度等级(一个字符串,例如
"easy"或"hard")
通过第一个变量修改设置后,使用第二个变量打印这三项设置,每项占一行,顺序如下:音乐音量、音效音量,然后是难度。
例如,如果输入是 50 和 hard,输出应为:
50
100
hard如果输入是 25 和 easy,输出应为:
25
100
easy请注意,音效音量仍为默认值 100,因为我们从未更改它;但音乐音量和难度反映了通过第一个变量所做的更改,这证明两个变量指向同一个单例实例!
亲手试试
-- main.lua:演示单例模式
-- 读取输入
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()
-- TODO:将 GameSettings 模块加载到一个变量中(例如 settings1)
-- TODO:再次将 GameSettings 模块加载到另一个变量中(例如 settings2)
-- (如果单例模式正常工作,两个变量会引用同一张表!)
-- TODO:用第一个变量完成以下操作:
-- 将音乐音量设为 newMusicVolume
-- 将难度设为 newDifficulty
-- TODO:用第二个变量打印设置
-- 每项各占一行,依次打印:musicVolume、sfxVolume、difficulty
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Lua 在线编译器