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 第 2 版] 的第 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 的作用域,则用 do 块将 soundssoundit 包围起来。

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 的名称被重复,代码进一步缩进/难看,看起来不像函数定义。此外,sounds 将被初始化,无论 soundit 是否被调用(从而造成加载时开销)。以下方法将 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

模式:<模式名称>

<模式描述>(在此添加更多模式)

另请参阅


最近更改 · 首选项
编辑 · 历史记录
最后编辑于 2017 年 8 月 2 日上午 9:58 GMT (差异)