模块模式和最佳实践
使用 local M = {} 模式,并清晰地公开公共 API。
模块模式和最佳实践 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。
M = {} 模式
通用的 Lua 模块模式是:声明一个局部表,填充它,然后返回它。所有公共符号都放入 M;所有私有辅助项都是普通的局部变量。这种方式清晰、简洁,并且在任何地方都适用。
-- The canonical module pattern
local M = {}
-- Private helper (not exported)
local function validate(x)
return type(x) == "number" and x >= 0
end
-- Public API
function M.sqrt(x)
assert(validate(x), "expected non-negative number")
return math.sqrt(x)
end
M.PI = math.pi
return M自引用模块
在模块内部,函数可以通过名称(M.foo())或作为局部变量来调用其他模块函数。使用局部变量的速度稍快;使用 M.foo() 则允许用户重写 M.foo,使内部调用使用重写后的函数(猴子补丁)。
local M = {}
-- Option A: use M.foo inside (allows override)
function M.double(n) return M.multiply(n, 2) end
function M.multiply(a, b) return a * b end
-- Option B: use local function (faster, no override)
local function mul(a, b) return a * b end
function M.triple(n) return mul(n, 3) end
return M单例模式
模块可以充当单例:它具有由所有调用者共享的可变内部状态。由于 require 会缓存模块,所有 require("mod") 调用都会获得具有相同状态的同一个对象。
-- config.lua (singleton)
local M = {}
local _config = {env="dev", logLevel="info"}
function M.set(key, val)
_config[key] = val
end
function M.get(key)
return _config[key]
end
function M.load(t)
for k,v in pairs(t) do _config[k]=v end
end
return M
-- All callers share the same config:作为命名空间的模块
可以将模块单纯用作命名空间,以避免污染全局表。将相关的常量和实用工具归到一个名称下,就像其他语言中的包一样。
-- constants.lua
local M = {
HTTP = {
OK=200, CREATED=201, NO_CONTENT=204,
BAD_REQUEST=400, UNAUTHORIZED=401,
NOT_FOUND=404, SERVER_ERROR=500,
},
COLORS = {RED="#FF0000", GREEN="#00FF00", BLUE="#0000FF"},
MAX_RETRIES = 3,
TIMEOUT_SEC = 30,
}
return M
-- local C = require("constants")
-- if status == C.HTTP.NOT_FOUND then ...工厂模块
这种模块导出的是工厂函数,而不是普通表。工厂会创建并返回拥有各自私有状态的新实例。这是模块级别的类模式。
-- logger.lua
local M = {}
function M.new(name, level)
level = level or "info"
local levels = {debug=1,info=2,warn=3,error=4}
local self = {}
function self.log(msgLevel, msg)
if levels[msgLevel] >= levels[level] then
print(string.format("[%s][%s] %s", name, msgLevel:upper(), msg))
end
end
function self.info(msg) self.log("info", msg) end
function self.warn(msg) self.log("warn", msg) end
function self.error(msg) self.log("error", msg) end
return self
end
return M模块初始化函数
有些模块在使用前需要配置。请提供一个 M.init(config) 函数,将配置存储在模块的私有状态中。这样可以实现依赖注入并提高可测试性。
-- db.lua
local M = {}
local pool = nil
function M.init(config)
pool = {
host = config.host or "localhost",
port = config.port or 5432,
connections = {},
}
print("DB initialized:", pool.host, pool.port)
end
function M.query(sql)
assert(pool, "call db.init() first")
-- ... execute query
return {}
end
return M不可变模块
使用 __newindex 阻止所有写入,可以防止用户意外修改模块 API。这对库模块尤其有用,因为意外的猴子补丁可能会导致功能异常。
local function freeze(t)
return setmetatable({}, {
__index = t,
__newindex = function(_, k, _)
error("module is read-only, cannot set: " .. tostring(k), 2)
end
})
end
local M = {}
function M.add(a, b) return a + b end
function M.sub(a, b) return a - b end
return freeze(M)使用 LDoc 编写文档
记录 Lua 模块时,常见约定是使用带有 --- 前缀的 LDoc 风格注释。虽然语言不会强制要求这种格式,但这些注释可以让文档生成工具自动生成 API 文档。
--- A utility module for string operations.
-- @module stringutils
local M = {}
--- Trim leading and trailing whitespace.
-- @param s string The input string.
-- @return string The trimmed string.
function M.trim(s)
return s:match("^%s*(.-)%s*$")
end
--- Count occurrences of a substring.
-- @param str string The string to search.
-- @param sub string The substring to count.
-- @return number Count of occurrences.
function M.count(str, sub)
local _, n = str:gsub(sub, "")
return n
end
return M测试模块
通过 require 模块并执行每个函数来测试它。您可以使用简单的测试运行器或 busted(Lua 测试框架)。请将测试放在单独的文件中,并使其路径与模块路径对应。
-- test/test_stringutils.lua
local su = require("stringutils")
local function test(name, fn)
local ok, err = pcall(fn)
if ok then print("[PASS] " .. name)
else print("[FAIL] " .. name .. ": " .. err)
end
end
test("trim removes spaces", function()
assert(su.trim(" hello ") == "hello")
end)
test("trim empty string", function()
assert(su.trim("") == "")
end)
test("count occurrences", function()
assert(su.count("banana", "a") == 3)
end)组合模块
复杂系统会组合多个模块。主入口会 require 并连接各个子模块。这种关注点分离可以让每个模块各司其职,并能够独立测试。
-- app.lua (main entry point)
local config = require("config")
local db = require("db")
local server = require("server")
-- Configure from environment
config.load({
dbHost = os.getenv("DB_HOST") or "localhost",
port = tonumber(os.getenv("PORT")) or 8080,
})
-- Wire modules together
db.init({host=config.get("dbHost"), port=5432})
server.init({port=config.get("port"), db=db})
server.start()快速检查
local M = {} ... return M 模块模式的主要用途是什么?
回顾:模块最佳实践
总结:
- 始终使用
local M = {} ... return M - 私有 = 文件级局部变量;公共 = M 字段
- 单例:require 会缓存模块实例
- 使用工厂函数管理每个实例的状态
- 使用 __newindex 冻结模块,以防止修改
- 在单独的文件中进行测试;使用 --- 注释编写文档
常见问题解答
「模块模式和最佳实践」课时是免费的吗?
是的 — 「模块模式和最佳实践」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。
「模块模式和最佳实践」这节课中我会学到什么?
使用 local M = {} 模式,并清晰地公开公共 API。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Lua Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「模块模式和最佳实践」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Lua Academy 课中编写并运行代码吗?
能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。