0Pricing
Lua Academy · 课时

模块模式和最佳实践

使用 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. require 函数
  2. 编写模块文件
  3. package.path 和 package.cpath
  4. 模块模式和最佳实践
← 返回 Lua Academy