Lua 设计模式

lua-users home
wiki

本页面旨在介绍 Lua 的设计模式。背景信息请参阅 [Wikipedia: 设计模式]

其他页面的设计模式

模式:全局收集器

传统上,如果我们想定义一个变量表或键值对,我们会使用表构造语法 {...}

-- traditional method.
local squares = {}; for n=1,10 do squares[n] = n^2 end
local v = {
  x = 50,
  squares = squares,
  hello = function() print("hello?") end
}

然而,通过适当定义一个 collect 函数,我们可以改为如下方式构造:

local v = collect(function()
  if true then x = 50 end
  squares = {}; for n=1,10 do squares[n] = n^2 end
  function hello() return "hello?" end
end)

请注意,一个潜在的好处是可以将代码语句穿插其中并构建键值对定义。但这会增加一些开销。

collect 的定义如下:

function collect(f)
  -- This collects globals defined in the given function.
  local collector = setmetatable({}, {__index = _G})
  -- Call function in collector environment
  setfenv(f, collector)()
  -- Extract collected variables.
  local result = {}; for k,v in pairs(collector) do result[k] = v end
  return result
end

测试套件

assert(v.x == 50)
assert(#v.squares == 10)
assert(v.hello() == "hello?")
assert(v.print == nil) -- ensure _G not included.
print("done")

这种机制在 Lua 5.1 模块系统中有所使用,其中收集函数在文件中给出,而 require/module 函数实现了收集机制(以及其他功能)。请参阅 [Programming in Lua 2nd Edition] 的第 15 章。

module("mymodule")
x = 50
color = "blue"
function hello() return "hello?" end

--DavidManura, 2006-10, Lua 5.1

模式:函数链 / 钩子

有很多有趣的方式可以在给定事件上执行一系列操作。我见过一种效率不太高的方式是这样的:

for _,v in pairs(files_in_directory) do
  dofile(v)
  if action then
    action(args)
  end
  action = nil
end
其中目录中的一个文件可能看起来像这样:
function action(something)
  print(something)
end
这是低效的;它需要在每次调用时重新解析所有内容,并且会覆盖一个名为“action”的全局变量。它也不能有效地处理权重。在 naim 中,我们使用一个用 C 实现的钩子系统,该系统创建了一堆链,我们可以向其中注册 C 和 Lua 操作。

我编写了一个系统,允许用户在 Lua 中创建自己的钩子链,这些链可以像函数一样执行。对我来说,语法似乎相当合乎逻辑。

require"hooks"
myhooks = {}
myhooks.test = hooks:new()
myhooks.ref1 = myhooks.test:insert(function (foo, bar) print("Test 1", foo, bar) end, 100)
myhooks.ref2 = myhooks.test:insert(function (foo, bar) print("Test 2", foo, bar) end, 50)
myhooks.ref3 = myhooks.test:insert(function (foo, bar) print("Test 3", foo, bar) end, 150)
print"--"
myhooks.test("Hello", "world")
myhooks.test:remove(myhooks.ref1)
print"--"
myhooks.test("Hello", "world")

运行这段代码会产生类似以下的输出:

--
Test 2  Hello   World
Test 1  Hello   World
Test 3  Hello   World
--
Test 2  Hello   World
Test 3  Hello   World

驱动此功能的代码可在 [1] 找到。待办事项:支持可写参数。这在 naim 中是必需的,如果希望修改通过的字符串;例如,一个过滤器模块可能希望将输入字符串中的所有“lol”实例替换为“<grin>”,然后将修改后的字符串传递给链中的其他钩子。欢迎贡献补丁。

-- JoshuaWise, 2007-02-01

模式:静态局部变量

有时会遇到一个冲突:一个变量应该被词法作用域限制在特定函数内,但其生命周期也应该比函数调用更长。在下面的例子中,sounds 表仅由 soundit 函数使用,这表明可以将其移入 soundit 函数内部,但每次函数调用时重建 sounds 会很浪费,所以程序员通常会将 sounds 放在外面。

local sounds = {
  a = "ay",
  b = "bee",
  c = "see",
  ....
}
local function soundit(x)
  assert(type(x) == "string")
  return sounds[x]
end

在 C 语言中,我们可以将 sounds 设为 soundit 内部的一个静态变量。在 Lua 中,如果想限制 sounds 的作用域,通常的建议是将 soundssoundit 包围在一个 do 块中:

local soundit; do
  local sounds = {
    a = "ay",
    b = "bee",
    c = "see",
    ....
  }
  function soundit(x)
    assert(type(x) == "string")
    return sounds[x]
  end
end
-- note: sounds not visible outside the do-block.

一个抱怨是,现在函数的实现分散在函数外部,soundit 这个名字被重复了,并且代码进一步缩进/变得丑陋,看起来不太像函数定义。此外,无论 soundit 是否被调用,sounds 都会被初始化(从而带来加载时的开销)。以下方法将 sounds 放在函数外部,但将其初始化移到函数内部。由于 or 的短路行为,它通常在调用时带来的额外开销很小:

  local soundit; do local sounds; function
soundit(x)
  sounds = sounds or {
    a = "ay",
    b = "bee",
    c = "see",
    ....
  }  
  assert(type(x) == "string")
  return sounds[x]
end end

事实上,我们可以放弃完美,让词法作用域溢出以增强可读性。

local sounds

local function soundit(x)
  sounds = sounds or {
    a = "ay",
    b = "bee",
    c = "see",
    ....
  }  
  assert(type(x) == "string")
  return sounds[x]
end

这里有两个涉及闭包构造的变体。它们比 do 块方法更整洁一些,但会带来至少构造一个临时函数的加载时开销。

local soundit = (function()
  local sounds = {
    a = "ay",
    b = "bee",
    c = "see",
    ....
  }  
  return function(x)
    assert(type(x) == "string")
    return sounds[x]
  end
end)()

local soundit = (function()
  local sounds
  return function(x)
    sounds = sounds or {
      a = "ay",
      b = "bee",
      c = "see",
      ....
    }  
    assert(type(x) == "string")
    return sounds[x]
  end
end)()

--DavidManura, 2007-03

模式:<Pattern Name>

<Pattern description> (在此处添加更多模式)

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2017 年 8 月 2 日凌晨 3:58 GMT (差异)