Lua 没有内置对 OO 的支持,但它允许你自己构建它。您能否分享一些实现面向对象的方法?
请为每个答案写一个例子。如果您有更多示例,请发布另一个答案。
我喜欢将 OOP 视为容器(对象)内的数据封装,以及可以使用该数据完成的操作子集。还有很多内容,但让我们假设这个简单的定义就是全部,并用它在 Lua 中构建一些东西(对其他 OO 实现的一些熟悉也可以对读者有很大的帮助)。
任何接触过 Lua 的人都知道,表是存储键值对的一种巧妙方式,与字符串结合使用,事情开始变得非常有趣:
local obj = {} -- a new table
obj["name"] = "John"
obj["age"] = 20
-- but there's a shortcut!
print("A person: " .. obj.name .. " of the age " .. obj.age)
作为表中键的字符串值的访问方式与 C 中的结构成员或 C++/Java 和类似语言中对象的公共成员非常相似。
现在来一个很酷的魔术:让我们将其与匿名函数结合起来。
-- assume the obj from last example
obj.hello = function ()
print("Hello!")
end
obj.goodbye = function ()
print("I must be going.")
end
obj.hello()
obj.goodbye()
很棒吧?我们现在可以将函数存储在表中,并且您可以再次看到它类似于其他 OOP 语言中方法的使用方式。但缺少一些东西。我们如何在方法定义中访问属于我们对象的数据?通常可以通过将表中函数的签名更改为如下形式来解决此问题:
-- assume the obj from last example
obj.inspect = function (self)
print("A person: " .. self.name .. " of the age " .. self.age)
end
obj.hello = function (self)
print(self.name .. ": Hello! I'm " .. self.name)
end
obj.goodbye = function (self)
print(self.name .. ": I must be going.")
end
-- now it receives the calling object as the first parameter
obj.inspect(obj) -- A person: John of age 20
obj.hello(obj) -- John: Hello! I'm John
obj.goodbye(obj) -- John: I must be going
这样就可以简单地解决这个问题了。也许与 Python 中的工作方式进行类比(方法总是有一个明确的自我)可以帮助您了解 Lua 中的工作方式。但是,在我们的方法调用中显式传递所有这些对象不是很不方便吗?是的,这也困扰着我,所以还有另一个捷径可以帮助你使用 OOP:
obj:hello() -- is the same as obj.hello(obj)
最后,我刚刚触及了如何做到这一点的皮毛。正如 Kevin Vermeer 的评论中所指出的,Lua Users Wiki 是有关此主题的极好的信息来源,在那里您可以了解如何实现本答案中忽略的 OOP 的另一个重要方面(私人成员,如何构造对象,继承,...)。请记住,这种做事方式是 Lua 哲学的一小部分,为您提供能够构建更高级构造的简单正交工具。
为了快速而肮脏的 oo 实现,我做了类似的事情 -
function newRGB(r,g,b)
return {
red=r;
green=g;
blue=b;
name='';
setName = function(self,name)
self.name=name;
end;
getName = function(self)
return self.name;
end;
tostring = function(self)
return self.name..' = {'..self.red..','..self.green..','..self.blue..'}'
end
}
end
然后可以像-
一样使用blue = newRGB(0,0,255);
blue:setName('blue');
yellow = newRGB(255,255,0);
yellow:setName('yellow');
print(yellow:tostring());
print(blue:tostring());
为了获得更全功能的方法,我将使用 eemrevnivek 提到的 oo 库。您还可以在here找到一个简单的类函数,它介于完整的库和快速而肮脏之间。
这已经得到解答,但无论如何,这是我的 oop 实现:middleclass。
该库提供了创建类、实例、继承、多态性和(原始)混合的最低限度,并且具有可接受的性能。
样品:
local class = require 'middleclass'
local Person = class('Person')
function Person:initialize(name)
self.name = name
end
function Person:speak()
print('Hi, I am ' .. self.name ..'.')
end
local AgedPerson = class('AgedPerson', Person) -- or Person:subclass('AgedPerson')
AgedPerson.static.ADULT_AGE = 18 --this is a class variable
function AgedPerson:initialize(name, age)
Person.initialize(self, name) -- this calls the parent's constructor (Person.initialize) on self
self.age = age
end
function AgedPerson:speak()
Person.speak(self) -- prints "Hi, I am xx."
if(self.age < AgedPerson.ADULT_AGE) then --accessing a class variable from an instance method
print('I am underaged.')
else
print('I am an adult.')
end
end
local p1 = AgedPerson:new('Billy the Kid', 13) -- this is equivalent to AgedPerson('Billy the Kid', 13) - the :new part is implicit
local p2 = AgedPerson:new('Luke Skywalker', 21)
p1:speak()
p2:speak()
输出:
Hi, I am Billy the Kid.
I am underaged.
Hi, I am Luke Skywalker.
I am an adult.
我通常使用的方法是这样的:
class = {} -- Will remain empty as class
mt = {} -- Will contain everything the instances will contain _by default_
mt.new = function(self,foo)
local inst={}
if type(foo) == "table" then
for k,v in pairs(foo) do
inst[k]=v
end
else
inst.foo=foo
end
return setmetatable(inst,getmetatable(class))
end
mt.print = function(self)
print("My foo is ",self.foo)
end
mt.foo= 4 --standard foo
mt.__index=mt -- Look up all inexistent indices in the metatable
setmetatable(class,mt)
i1=class:new() -- use default foo
i1:print()
i2=class:new(42)
i2:print()
i3=class:new{foo=123,print=function(self) print("Fancy printing my foo:",self.foo) end}
嗯,结论:通过元表和一些聪明的思考,一切皆有可能:元表是处理类时真正的魔力。
我看到的最好的解决方案是不要在 Lua 中实现 OO,因为它不自然且不完整,因此需要很多行;相反,使用 luabridge 或 luabind 在 C++ 中实现它,它自然而强大!
使用 LuaBridge 的简约示例:
m.class_<MyClass>("MyClass")
.constructor<void (*) (/* parameter types */)>()
.method("method1", &MyClass::method1)
.property_rw("property2", &MyClass::getter2, &MyClass::setter2)
.property_ro("property3", &MyClass::property3)
这将转化为自然的 lua 语法:
c=MyClass()
c.method1()
c.property2 = c.property3 * 2
do_stuff(c.property3)
还支持一级继承...
对我来说,封装和解耦是我在 Lua 中实现类时最重要的因素。因此,对于大多数情况,当我愿意牺牲一点速度和内存来为我的类方法和属性提供一个非常强大的独立环境时,使用基于闭包的方法就足够了。
考虑这个会计课程的例子:
function Account(self)
local balance = 0
self.deposit = function (v)
balance = balance + v
end
self.withdraw = function (v)
if v > balance then error"insufficient funds" end
balance = balance - v
end
return self
end
现在与大多数等效的元表方法进行比较和对比:
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:deposit (v)
self.balance = self.balance + v
end
function Account:withdraw (v)
if v > self.balance then error"insufficient funds" end
self.balance = self.balance - v
end
闭包的最大优点是它们为对象提供了一个真正私有的环境,使其能够不受干扰地完成其工作。不需要额外的样板代码。
然而,缺点是每个实例化的对象都会导致新的多个闭包。这种额外的开销可能会也可能不会被接受,具体取决于您的应用程序。