【问题标题】:What's the difference between these two Lua examples? Is one better?这两个 Lua 示例有什么区别?一个更好吗?
【发布时间】:2015-02-19 01:36:19
【问题描述】:

我刚刚开始使用 Lua。在我向 (the Ghosts & Monsters Corona open source) 学习的示例中,我反复看到这种模式。

local director = require("director")

local mainGroup = display.newGroup()

local function main()

   mainGroup:insert(director.directorView)

   openfeint = require ("openfeint")
   openfeint.init( "App Key Here", "App Secret Here", "Ghosts vs. Monsters", "App ID Here" )

   director:changeScene( "loadmainmenu" )

   return true
end

main()

这是有经验的 Lua 程序员推荐的某种约定,还是这样做有真正的优势?你为什么不一起跳过这个函数并这样做:

local director = require("director")

local mainGroup = display.newGroup()

mainGroup:insert(director.directorView)

local openfeint = require ("openfeint")
openfeint.init( "App Key Here", "App Secret Here", "Ghosts vs. Monsters", "App ID Here" )

director:changeScene( "loadmainmenu" )

第一种风格比第二种风格有什么隐含的好处吗?谢谢!

【问题讨论】:

  • 你确定你已经正确简化了吗?您能否以完整形式链接到其中一些示例?
  • 是的,您发布的“模式”似乎毫无意义。你说你看到“很多”,你能链接到一个例子吗?
  • 我用一个更完整的例子编辑了这个问题,并引用了你可以下载完整源档案的地方。如果 Lua 和我知道的其他语言一样,那么第二种形式似乎事半功倍。

标签: lua


【解决方案1】:

这是有经验的 Lua 程序员推荐的某种约定,还是这样做有真正的优势?

这不是典型的。优点是对象状态是私有的,但这还不足以推荐它。

我反复看到这种模式。

我以前从未见过,而且在您发布的来源中只发生过一次。

编辑:添加对本文下方 cmets 中提出的问题的回复。

访问外部局部变量的函数绑定到这些变量,称为“闭包”。 Lua(出于历史原因)将这些绑定变量称为“上值”。例如:

local function counter()
   local i = 1
   return function()
      print(i)
      i = i + 1
   end
end

local a, b = counter(), counter()
a() a() a() b() --> 1 2 3 1

ab 是绑定到 i 的不同副本的闭包,从输出中可以看出。换句话说,您可以将闭包视为具有自己私有状态的函数。您可以使用它来模拟对象:

function Point(x,y)
   local p = {}
   function p.getX() -- syntax sugar for p.getX = function()
      return x
   end
   function p.setX(x_)
      x = x_
   end
   -- for brevity, not implementing a setter/getter for y
   return p
end

p1 = Point(10,20)
p1.setX(50)
print(p1.getX())

Point 返回一个闭包表,每个闭包都绑定到局部变量 xy。该表不包含点的状态,闭包本身通过它们的 upvalues 包含。重要的一点是,每次调用 Point 时,它都会创建 new 闭包,如果您有大量对象,这不是很有效。

在 Lua 中创建类的另一种方法是创建以表为第一个参数的函数,并将状态存储在表中:

function Point(x,y)
   local p = {x=x,y=y}
   function p:getX() -- syntax sugar for p.getX = function(self)
      return self.x
   end
   function p:setX(x)
      self.x = x
   end
   return p
end

p1 = Point(10,20)
p1:setX(50) -- syntax sugar for p1.setX(p1, 50)
print(p1:getX()) -- syntax sugar for p1.getX(p1)

到目前为止,我们仍在为每个方法创建新副本,但现在我们不依赖于状态的上值,我们可以解决这个问题:

PointClass = {}
function PointClass:getX() return self.x end
function PointClass:setX(x) self.x = x end
function Point(x,y)
   return {
      x = x,
      y = y,
      getX = PointClass.getX,
      setX = PointClass.getY,
   }
end

现在方法被创建一次,所有Point 实例共享相同的闭包。一个更好的方法是使用 Lua 的元编程工具使新的Point 实例自动在PointClass 中查找在实例本身中找不到的方法:

PointClass = {}
PointClass.__index = PointClass -- metamethod
function PointClass:getX() return self.x end
function PointClass:setX(x) self.x = x end
function Point(x,y)
   return setmetatable({x=x,y=y}, PointClass)
end

p1 = Point(10,20)
-- the p1 table does not itself contain a setX member, but p1 has a metatable, so 
-- when an indexing operation fails, Lua will look in the metatable for an __index
-- metamethod. If that metamethod is a table, Lua will look for getX in that table,
-- resolving p1.setX to PointClass.setX.
p1:setX(50)

这是在 Lua 中创建类的更惯用的方式。它更节省内存且更灵活(特别是,它可以轻松实现继承)。

【讨论】:

  • Mud,它在那个特定文件 (main.lua) 中出现过一次,但作者在整个代码中都使用了该模式。存档中的许多文件(level*.lua、load*.lua、)都使用了这种模式。也就是说,包含在存档中但由其他作者编写的director.lua 似乎没有这种模式 - 这让我相信这是 Ghosts & Monster 的作者个人风格。
  • 是的,这是他的事。一般的代码不是很惯用的 Lua。特别是,他创建类的方法非常不寻常:为每个实例方法创建一个新的闭包,将状态存储在 upvalues 中,而不是为将状态存储在表中的类的一组共享方法。这是相当浪费的,但它也是创建真正私有状态的一种方式。这可能是他正在做的事情,但是在代码的其他区域,他使用表来表示状态(有时同一个类将其状态的一部分存储在 upvalues 中,部分存储在表中)。
  • 泥,谢谢讨论。再说一次,因为我是 Lua 的新手,你介意解释一下你所说的 upvalues 是什么意思吗?谢谢!
  • 是的,我会解释我的意思,但是这些 cmets 非常有限,所以我将编辑上面的答案。
  • 最后一段代码在我看来不正确。在 p1 = Point(10,20) 之后写入 print(p1:getX()) 会打印 nil 而不是 10。
【解决方案2】:

我经常用这种方式编写自己的 Lua 脚本,因为在这种情况下它提高了可读性:

function main()
    helper1( helper2( arg[1] ) )
    helper3()
end

function helper1( foo )
    print( foo )
end

function helper2( bar )
    return bar*bar
end

function helper3()
    print( 'hello world!' )
end

main()

这样“主”代码在顶部,但我仍然可以在它执行之前定义必要的全局函数。

一个简单的技巧,真的。除了可读性,我想不出任何理由这样做。

【讨论】:

    【解决方案3】:

    第一种样式可以用来提高可读性,但我宁愿给函数一个有意义的名字而不是 main 或者干脆不使用这个函数。

    顺便说一句,我认为命名代码块总是一个好习惯,即将它们放入函数或方法中。它有助于解释您使用那段代码的意图,并鼓励重用。

    【讨论】:

      【解决方案4】:

      我没有看到你展示的第一种风格。但是,如果它在底部显示类似if arg then main() end 的内容,那么除了作为独立脚本之外,该脚本可能(只是可能)作为可加载的“库”有用。也就是说,有一个main() 像 C 的味道,而不是 Lua;我认为你的质疑是对的。

      【讨论】:

        猜你喜欢
        • 2013-11-23
        • 1970-01-01
        • 2018-01-13
        • 1970-01-01
        • 2019-11-15
        • 1970-01-01
        • 1970-01-01
        • 2012-06-12
        • 2016-10-06
        相关资源
        最近更新 更多