我试图写一个类系统,通过对音符和节奏进行排列来产生旋律。我将在工具" xStream"中使用它。在" Renoise"软件。我的例子是我真正拥有的一个非常愚蠢和通用的版本。
我也将此作为一个学习机会。我的OOP技能很弱,而且我仍然没有完全将脑袋缠绕在metatables上。如果我完全遗漏了某些东西,那就是先发制人的抱歉。我在第三版 Lua中的编程中使用基本OOP示例的样式完成了我的所有代码。
我想要做的是将具有类似功能的类方法全部分组到'嵌套'表。例如,一个'旋律排列表,一个节奏排列表',一个杂项效用方法表等。
在代码中,我有一个班级SomeClass
。它有两种类型的打印功能:print1_notes
位于'主表中' (即SomeClass
的关键字)。 print2.notes
是一个嵌套的'表print2
,SomeClass
的关键字(即notes
只是SomeClass.print2
的关键字。)
我可以打电话给print1_notes
就好了。问题是当我用冒号运算符调用print2
方法时。
如果我不使用糖(例如,obj.print2.notes(obj)
),那么没问题。但是当我这样做时(例如obj.print2:notes()
),我得到错误"尝试(等等)一个函数值..."。
SomeClass.print2
中的方法是否可以访问SomeClass
中的密钥?他们的访问方式与非嵌套表键的访问方式相同吗? print2
是什么时,我认为__index
需要一个SomeClass
密钥告诉其方法在self.a_key
中查找密钥。但是self
(在print2
方法中)并不是一个关键。它实际上只是SomeClass
的别名。是吗?似乎与嵌套表存在差异。感谢。对不起,如果需要移动或者之前有人问过这个问题。
SomeClass = {
new = function (self, t)
t = t or {}
setmetatable(t, self)
self.__index = function (_, key)
return self[key]
end
--should I add a setmetatable here? perhaps:
--setmetatable(self.print2, self)
return t
end,
notes = {},
set_notes = function (self, t)
self.notes = t or {}
self.N = #self.notes
end,
print1_notes = function (self)
print("There are "..tostring(self.N).." notes :", table.unpack(self.notes))
end,
--table of different print functions
print2 = {
notes = function (self)
--is self an alias for SomeClass?
assert(self.notes, "Error: self.notes = nil")
print("There are "..tostring(self.N).." notes :", table.unpack(self.notes))
end,
first_note = function (self)
fn = self.notes[1]
print("first note is: ", fn)
end,
},
}
obj = SomeClass:new()
obj:set_notes{ 10,14,5, 10,14,5, 17 }
print("\ncalling print1_notes without sugar:")
obj.print1_notes(obj)
print("\ncalling print1_notes with sugar:")
obj:print1_notes()
print("\ncalling print2.notes without sugar")
obj.print2.notes(obj)
print("\ncalling print2.notes with sugar")
obj.print2:notes() --this gives an error: "attempt to get length of a function value"
obj.print2.first_note(obj) --this works fine
obj.print2:first_note() --this gives an error:
-- "attempt to index a function value (field 'notes')"
编辑代码:tostring(N)
的实例需要替换为tostring(self.N)
。
编辑:奇怪的错误与SomeClass.print2.notes
有像SomeClass.notes
这样的笔记成员的事实有关。 SomeClass.print2.first_note
避免了这种复杂情况。 (当我回答时,我会解释更多)
答案 0 :(得分:0)
从您的示例中,我猜Class
也有一个字段notes
,您尝试在print2.notes()
中访问
这里的问题是lua并没有真正实现教科书对象的定位;如果你致电class:print_notes(...)
,你真的只需致电class.print_notes(class, ...)
。如果您想致电class.print2.notes(class)
,则无法class.print2:notes()
,因为这相当于致电class.print2.notes(class.print2)
。你也不能写class:print2.notes()
,因为它的语法无效;你只能使用:
索引函数并在那里调用它们。
编辑:至于你得到的错误,class.print2.notes()
可能会尝试访问notes
的{{1}}成员,这可能是一个表,但由于冒号语法,而是尝试在class
中访问notes()
,这是一个函数,在尝试索引时会导致错误。
至于实际的解决方案,我说你应该首先重新考虑代码的结构。将函数组织到类中的命名空间是一种有点奇怪的方法,并且是一个强有力的指标,表明你的类是膨胀的并且做得比它应该更多,或者它不应该是它自己的一个类,而是一个库,几个类,或者甚至是一个简单的功能。
如果两种打印方法最终打印笔记,并且笔记是一个数组,那么为什么不用两种打印方法扩展该数组呢? luas OO的美妙之处在于对象和数据之间没有明确的界限,因此它取决于你看待它的方式。尽可能尝试使用这种力量,并且不要过分使用OO设计的教科书,这不是lua擅长或曾经用过的。
答案 1 :(得分:0)
在考虑了一段时间的问题之后,我注意到还有另一种方法来解决它并保持命名空间的想法。
同样,您不能使用冒号语法将类传递给不在类中但在类的名称空间中的函数。您可以的内容如下:
EventUser
正如您所看到的,每个实例都有自己的命名空间表,但它们都具有对同一共享函数的引用以及对它们所属实例的引用。当您致电local function print2(instance)
-- Does things
end
local function wrapper(namespace)
print2(namespace.instance)
end
function someClass.new()
...
notes = {print=wrapper,instance=t} -- every instance needs its own namespace table
...
end
时,它最终会在instance.notes:print()
上拨打print
,但该功能仅调用instance.notes
上的真实功能,该功能指向instance.notes.instance
答案 2 :(得分:0)
我提出了各种各样的解决方案。简而言之:print2
中的所有功能都必须以self = getmetatable(self)
开头。这是将self
(原obj.print2
)转换为obj
。
此外,如果此答案太长或违反任何准则,请抱歉。
重申一下,我想要另一张表SomeClass.print2
,其中包含所有与打印相关的功能。如果我想打印笔记,我会做
obj.print2:notes()
--sugar for
obj.print2.notes(obj.print2)
当然,当我这样做时,self.notes
内的SomeClass.print2.notes
会引用obj.print2.notes
(而不是obj.notes
)。即使我将函数重命名为SomeClass.print2.the_notes
,这也是一个问题。
所以我所做的就是将SomeClass:new
更改为:
SomeClass:new = function (self, t)
t = t or {} --
setmetatable(t, self) --
self.__index = function (_, k) --
return self[k] --
end -- same from before
mt = {}
mt.print2 = {}
mt.print2.__index = SomeClass.print2 -- only fixes calls to obj.print2:foo()
t.print2 = {}
setmetatable( t.print2, mt.print2)
setmetatable(mt.print2, t)
t.__index = t -- fixes references to self.key inside print2 functions
return t
end
SomeClass.print2
现在看起来应该是这样的:
SomeClass.print2 = {
notes = function (self)
self = getmetatable(self) -- self is now mt.print2
-- self.notes will become obj.notes
self = getmetatable(self) -- self should just be obj now
-- but this is unnecessary
print("There are "..tostring(self.N).." notes :", table.unpack(self.notes))
end,
first_note = function (self)
self = getmetatable(self)
fn = self.notes[1]
print("first note is: ", fn)
end,
}
基本上,对obj.print2.foo
的任何调用都应返回SomeClass.print2.foo
。 (见mt.print2.__index
)。
SomeClass.print2
中的每个功能必须至少有一个' self = getmetatable(self)
'在函数体的顶部**。然后,在SomeClass.print2.foo()
内,self
为mt.print2
。那么,self.key
应该成为:
obj.key
为key
或notes
,则为N
obj.key
为零,则变为SomeClass.key
而不是 **建议使用第二个self = getmetatable(self)
,但这是可选的。
循序渐进,我们拥有的是
obj = SomeClass:new()
创建新对象
obj
在obj.print2
mt
obj
mt.print2
是obj.print2
的元表,而mt.print2
的元表是obj obj:set_notes{10,20,30}
)obj.print2:notes()
,即obj.print2.notes(obj.print2)
somekey
'是notes
__index
obj.print2
函数
mt.__index
返回SomeClass.print2.somekey
obj.print2.notes(obj.print2)
现在是SomeClass.print2.notes(obj.print2)
self
是obj.print2
obj.notes
self.notes
self
更改为其metatable一次。我们self = getmetatable(self)
。self
现在是mt.print2
self.notes
,那将是mt.print2.notes
mt.print2.notes
是nil obj.__index
,因为obj
是mt.print2
mt.print2.notes
应该成为obj.notes
self.notes
变为obj.notes
self
是mt.print2
self = getmetatable(self)
self
将成为obj
,因为它是mt.print2
这是一个非常难看的解决方案,我承认。但它的确有效。
我讨厌将self = getmetatable(self)
放在SomeClass.print2
中任何函数体的顶部。如果在self
内将obj
转换为mt.print2.__index
,则可以避免这种情况。我非常确定这是不可能的,因为mt.print2.__index
只能返回一个对象(即函数SomeClass.print2.foo
)。
我mt
使用mt.print2
的表格的原因是我可以添加更多这些' namespaced'函数SomeClass
。如果我想要一个名称空间m_notes
用于转换注释的函数,我只需将这些语句添加到SomeClass:new()
:
mt.m_notes = {}
mt.m_notes.__index = SomeClass.m_notes
t.m_notes = {}
setmetatable( t.m_notes, mt.m_notes )
setmetatable(mt.m_notes, t)
事实上,我可以创建一个为任何新命名空间执行此操作的函数:
add_namespace = function( t, mt, key_string )
mt[key_string] = {}
mt[key_string].__index = SomeClass[key_string]
t[key_string] = {}
setmetatable( t[key_string], mt[key_string] )
setmetatable(mt[key_string], t)
end
也许最好让mt
成为t
中的成员。因此mt
只是t.mt
而mt.print2
将是t.mt.print2
等等。然后,如果需要,可以稍后访问它(例如,出于封装目的)。单独拥有mt
并没有任何问题;调用SomeClass:new()
将始终创建与新对象关联的新mt
。
请注意,如果您能够通过此技巧考虑任何性能问题/增强功能。或者,如果我可以使它看起来更优雅。如果您能想出以任何方式删除每个self = getmetatable(self)
函数定义顶部的print2
,请务必告诉我。