Lua,class-table和实例化用什么方式?
Lua, which way to do class-table and instantiation?
问题来自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
One additional photo to depict the question more clearly
虽然教程中的实现成功打印了‘I saywoof’,但 myDog 的 metatable 显然不如我们预期的那么理想。因此我的解决方案如下(差异在 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' 并且具有更精确的 table 结构。
我想知道哪种实现是正确的,教程中的还是我的?
另外想知道教程里的代码为什么会生成Dog的metatable.
的迭代定义
让我们看一下 table Dog
的结构,它在构造 Dog 对象后有一个 __index
元方法集,如下所示:
Dog = {} --> table: 000000000079a510
Dog.__index = Dog --> table: 000000000079a510
当您打印 Dog
table 时,键 __index
具有包含 table 的值,这导致递归。标准 Lua 不会很好地打印 tables,所以这个 print
函数必须在 ~5 级后停止(即:__index : {...}
它停止递归的地方)。正如@siffiejoe 在评论中提到的,这是一种对对象方法和元方法使用单个 table 的技术。
关于哪个实现是正确的;在 Lua 中有多种创建对象的方法。 class 的例子虽然没有错,但恕我直言,它确实不必要地使用了全局变量。它的实现通过 Dog
和 newObj
泄漏到全球环境中。孤立起来不是什么大问题,但是当它成为更大程序的一部分时,这可能会成为难以发现错误的来源。另一种技术是将 class 作为模块来实现。使用局部变量来实现并仅导出实例化新对象所需的内容。
例如,让我们看一下 Dog
class:
的重构
-- 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
可以通过链接 metatable 并在 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;
}
这样每个 class 都封装在自己的模块中,不会将实现细节泄漏到全局环境中。
-- 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 中创建 classes,这只是其中的一个例子。无论您选择实现对象,保持全局环境清洁仍然是一个很好的做法。
问题来自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
One additional photo to depict the question more clearly
虽然教程中的实现成功打印了‘I saywoof’,但 myDog 的 metatable 显然不如我们预期的那么理想。因此我的解决方案如下(差异在 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' 并且具有更精确的 table 结构。 我想知道哪种实现是正确的,教程中的还是我的? 另外想知道教程里的代码为什么会生成Dog的metatable.
的迭代定义让我们看一下 table Dog
的结构,它在构造 Dog 对象后有一个 __index
元方法集,如下所示:
Dog = {} --> table: 000000000079a510
Dog.__index = Dog --> table: 000000000079a510
当您打印 Dog
table 时,键 __index
具有包含 table 的值,这导致递归。标准 Lua 不会很好地打印 tables,所以这个 print
函数必须在 ~5 级后停止(即:__index : {...}
它停止递归的地方)。正如@siffiejoe 在评论中提到的,这是一种对对象方法和元方法使用单个 table 的技术。
关于哪个实现是正确的;在 Lua 中有多种创建对象的方法。 class 的例子虽然没有错,但恕我直言,它确实不必要地使用了全局变量。它的实现通过 Dog
和 newObj
泄漏到全球环境中。孤立起来不是什么大问题,但是当它成为更大程序的一部分时,这可能会成为难以发现错误的来源。另一种技术是将 class 作为模块来实现。使用局部变量来实现并仅导出实例化新对象所需的内容。
例如,让我们看一下 Dog
class:
-- 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
可以通过链接 metatable 并在 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;
}
这样每个 class 都封装在自己的模块中,不会将实现细节泄漏到全局环境中。
-- 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 中创建 classes,这只是其中的一个例子。无论您选择实现对象,保持全局环境清洁仍然是一个很好的做法。