4

问题源自http://tylerneylon.com/a/learn-lua/ 教程包含代码:

Dog = {dog1 = 'original dog class'}
function Dog.new(self, ... )
    newObj = {sound = 'woof'}
    self.__index = self
    return setmetatable(newObj, self)
end

function Dog.makeSound(self, ... )
    print('I say' .. self.sound)
end

print('Dog=', Dog)
print('Dog.metatable=', getmetatable(Dog))  -- this will output nothing

myDog = Dog.new(Dog)
print('\nmyDog=', myDog)
print('myDog.metatable=', getmetatable(myDog))
myDog.makeSound(myDog)

这是教程中上述代码的结果:

wirelessprvnat-172-17-106-141:Programming frankhe$ th test2.lua
Dog=  {
  makeSound : function: 0x0a6cec20
  dog1 : "original dog class"
  new : function: 0x0a6cec00
}
Dog.metatable=  nil 

myDog=  {
  sound : "woof"
}
myDog.metatable=  {
  makeSound : function: 0x0a6cec20
  __index : 
    {
      makeSound : function: 0x0a6cec20
      __index : 
        {
          makeSound : function: 0x0a6cec20
          __index : 
            {
              makeSound : function: 0x0a6cec20
              __index : 
                {
                  makeSound : function: 0x0a6cec20
                  __index : {...}
                  dog1 : "original dog class"
                  new : function: 0x0a6cec00
                }
              dog1 : "original dog class"
              new : function: 0x0a6cec00
            }
          dog1 : "original dog class"
          new : function: 0x0a6cec00
        }
      dog1 : "original dog class"
      new : function: 0x0a6cec00
    }
  dog1 : "original dog class"
  new : function: 0x0a6cec00
}
I saywoof

一张额外的照片来更清楚地描述问题

尽管教程中的实现成功打印了“I saywoof”,但 myDog 的元表显然不像我们预期的那样理想。因此我的解决方案如下(不同之处在 Dog.new 中):

function Dog.new(self, ... )
    newObj = {sound = 'woof'}
    return setmetatable(newObj, {__index = self})
end

我的解决方案的结果:

wirelessprvnat-172-17-106-141:Programming frankhe$ th test2.lua
Dog=  {
  makeSound : function: 0x0d7f2978
  dog1 : "original dog class"
  new : function: 0x0d7f2958
}
Dog.metatable=  nil 

myDog=  {
  sound : "woof"
}
myDog.metatable=  {
  __index : 
    {
      makeSound : function: 0x0d7f2978
      dog1 : "original dog class"
      new : function: 0x0d7f2958
    }
}
I saywoof

我的代码打印出“I saywoof”并具有更精确的表结构。我想知道哪个实现是正确的,教程中的还是我的?另外,我想知道为什么教程中的代码会生成 Dog 的元表的迭代定义。

4

1 回答 1

6

让我们看一下 table 的结构Dog,在构建 Dog 对象后,它的__index元方法集如下所示:

Dog = {}            --> table: 000000000079a510     
Dog.__index = Dog   --> table: 000000000079a510

当您打印Dog表时,键__index具有其包含表的值,这会导致递归。标准 Lua 不能很好地打印表格,所以这个print函数必须在大约 5 级之后停止(即:__index : {...}它停止递归的地方)。正如@siffiejoe 在评论中提到的,这是一种将单个表用于对象方法和元方法的技术。

关于哪种实施是正确的;在 Lua 中有很多方法可以创建对象。示例类虽然没有错,但恕我直言,它不必要地使用了全局变量。Dog它的实现通过和泄漏到全球环境中newObj。单独来看问题不大,但是当作为较大程序的一部分时,这可能是难以发现错误的根源。另一种技术是将您的类实现为模块。使用局部变量来实现并仅导出实例化新对象所需的内容。

例如,让我们看一个Dog类的重构:

-- class: dog.lua
--
local Dog = {}     -- the objects implementation
Dog.__index = Dog  -- also, its own metatable

-- instantiate a Dog object:
local function new(name, sound)
    local self = {
        name = name,
        sound = sound or 'woof'
    }
    return setmetatable(self, Dog)
end

-- implement object methods:
function Dog.say(self)
    print(('<%s> says: %s'):format(self.name, self.sound))
end

-- implement object metamethods (unique to Dog objects):
function Dog.__tostring(self)
    return ('Dog: %s'):format(self.name)
end

-- module exports:
return {
    new = new;       -- Dog constructor
    __object = Dog;  -- Dog object table/metatable
}

该模块导出一个构造函数,该构造函数知道如何Dog在不需要全局对象的情况下构建对象。

-- original example:
myDog = Dog.new(Dog)  --> must pass in the global Dog table to create new objects

-- vs --

-- refactored example:
local Dog = require 'dog'   --> Dog object factory
local myDog = Dog.new()     --> instantiate new Dog

可以通过链接元表并在函数中调用父构造new函数来处理继承:

-- class: colorfuldog.lua
--
local Dog = require 'dog'   -- import the parent class

local ColorfulDog = setmetatable({}, Dog.__object)  -- inherit from Dog
ColorfulDog.__index = ColorfulDog                   -- also, its own metatable

-- instantiate a new ColorfulDog object:
local function new(name, sound, color)
    local self = Dog.new(name, sound)  -- construct the parent first
    self.color = color
    return setmetatable(self, ColorfulDog)
end

-- implement or override object methods:
function ColorfulDog.lookat(self)
    print(('<%s> looks: %s'):format(self.name, self.color))
end

-- implement object metamethods (unique to ColorfulDog objects):
function ColorfulDog.__tostring(self)
    return ('ColorfulDog: %s'):format(self.name)
end

-- module exports
return {
    new = new;
    __object = ColorfulDog;
}

这样每个类都封装在自己的模块中,不会将实现细节泄漏到全局环境中。

-- script: test.lua
--
local Dog = require 'dog'
local ColorfulDog = require 'colorfuldog'

local d1 = Dog.new 'Rover'
local d2 = Dog.new('Max', 'arf!')
local d3 = ColorfulDog.new('Lassie', 'ruff', 'brown')

d1:say()  -- sugar for d1.say(d1)
d2:say()
d3:say()  -- inherited from Dog
d3:lookat()

print(d1, d2, d3) 

运行上述输出:

$ lua test.lua
<Rover> says: woof
<Max> says: arf!
<Lassie> says: ruff
<Lassie> looks: brown
Dog: Rover      Dog: Max        ColorfulDog: Lassie

就像我之前说的,在 Lua 中创建类的方法有很多这只是其中的一个例子。无论您选择实现对象,它仍然是保持全局环境清洁的好习惯。

于 2016-05-09T05:57:13.813 回答