Menu
CoddyTech

单例表

本课属于 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 中并重复使用。

与会创建新对象的工厂模式不同,单例模式可以确保整个应用中的状态共享。这非常适合全局设置,代码中的多个部分都需要以一致的方式访问和修改这些设置。

challenge icon

挑战

简单

让我们使用单例模式来构建一个游戏设置管理器!你将创建一个配置模块,确保程序的每个部分访问的都是完全相同的设置,无论该模块被导入多少次。

你将把代码放在两个文件中:

  • GameSettings.lua:创建一个保存游戏配置的单例模块。设置表应包含 musicVolume(初始值为 70)、sfxVolume(初始值为 100)和 difficulty(初始值为 "normal")。添加三个方法::setMusicVolume(value)、:setSfxVolume(value) 和 :setDifficulty(level),用于修改这些设置。记得在模块末尾返回这个表!
  • main.lua:将 GameSettings 模块导入两次,并分别存入不同的变量,以证明单例模式有效。使用第一个变量更改音乐音量和难度。然后使用第二个变量打印全部三项设置。如果单例模式正常工作,两个变量将引用同一个表,因此通过其中一个变量所做的更改也能通过另一个变量看到。

你将收到两个输入:

  1. 新的音乐音量(一个数字)
  2. 新的难度等级(一个字符串,例如 "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
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Lua 在线编译器