[英]How to create a class, subclass and properties in Lua?
I'm having a hard time grokking classes in Lua . 我很难在Lua学习课程。 Fruitless googling led me to ideas about meta-tables, and implied that third-party libraries are necessary to simulate/write classes.
毫无结果的谷歌搜索让我了解了关于元表的想法,并暗示第三方库是模拟/编写类所必需的。
Here's a sample (just because I've noticed I get better answers when I provide sample code): 这是一个示例(仅仅因为我注意到在提供示例代码时我得到了更好的答案):
public class ElectronicDevice
{
protected bool _isOn;
public bool IsOn { get { return _isOn; } set { _isOn = value; } }
public void Reboot(){_isOn = false; ResetHardware();_isOn = true; }
}
public class Router : ElectronicDevice
{
}
public class Modem :ElectronicDevice
{
public void WarDialNeighborhood(string areaCode)
{
ElectronicDevice cisco = new Router();
cisco.Reboot();
Reboot();
if (_isOn)
StartDialing(areaCode);
}
}
Here is my first attempt to translate the above using the technique suggested by Javier. 这是我第一次尝试使用Javier建议的技术来翻译上述内容。
I took the advice of RBerteig. 我接受了RBerteig的建议。 However, invocations on derived classes still yield:
"attempt to call method 'methodName' (a nil value)"
但是,派生类的调用仍然会产生:
"attempt to call method 'methodName' (a nil value)"
--Everything is a table
ElectronicDevice = {};
--Magic happens
mt = {__index=ElectronicDevice};
--This must be a constructor
function ElectronicDeviceFactory ()
-- Seems that the metatable holds the fields
return setmetatable ({isOn=true}, mt)
end
-- Simulate properties with get/set functions
function ElectronicDevice:getIsOn() return self.isOn end
function ElectronicDevice:setIsOn(value) self.isOn = value end
function ElectronicDevice:Reboot() self.isOn = false;
self:ResetHardware(); self.isOn = true; end
function ElectronicDevice:ResetHardware() print('resetting hardware...') end
Router = {};
mt_for_router = {__index=Router}
--Router inherits from ElectronicDevice
Router = setmetatable({},{__index=ElectronicDevice});
--Constructor for subclass, not sure if metatable is supposed to be different
function RouterFactory ()
return setmetatable ({},mt_for_router)
end
Modem ={};
mt_for_modem = {__index=Modem}
--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index=ElectronicDevice});
--Constructor for subclass, not sure if metatable is supposed to be different
function ModemFactory ()
return setmetatable ({},mt_for_modem)
end
function Modem:WarDialNeighborhood(areaCode)
cisco = RouterFactory();
--polymorphism
cisco.Reboot(); --Call reboot on a router
self.Reboot(); --Call reboot on a modem
if (self.isOn) then self:StartDialing(areaCode) end;
end
function Modem:StartDialing(areaCode)
print('now dialing all numbers in ' .. areaCode);
end
testDevice = ElectronicDeviceFactory();
print("The device is on? " .. (testDevice:getIsOn() and "yes" or "no") );
testDevice:Reboot(); --Ok
testRouter = RouterFactory();
testRouter:ResetHardware(); -- nil value
testModem = ModemFactory();
testModem:StartDialing('123'); -- nil value
Here's an example literal transcription of your code, with a helpful Class
library that could be moved to another file. 这是代码的示例文字转录,带有一个有用的
Class
,可以移动到另一个文件。
This is by no means a canonical implementation of Class
; 这绝不是
Class
的规范实现; feel free to define your object model however you like. 随意定义您喜欢的对象模型。
Class = {}
function Class:new(super)
local class, metatable, properties = {}, {}, {}
class.metatable = metatable
class.properties = properties
function metatable:__index(key)
local prop = properties[key]
if prop then
return prop.get(self)
elseif class[key] ~= nil then
return class[key]
elseif super then
return super.metatable.__index(self, key)
else
return nil
end
end
function metatable:__newindex(key, value)
local prop = properties[key]
if prop then
return prop.set(self, value)
elseif super then
return super.metatable.__newindex(self, key, value)
else
rawset(self, key, value)
end
end
function class:new(...)
local obj = setmetatable({}, self.metatable)
if obj.__new then
obj:__new(...)
end
return obj
end
return class
end
ElectronicDevice = Class:new()
function ElectronicDevice:__new()
self.isOn = false
end
ElectronicDevice.properties.isOn = {}
function ElectronicDevice.properties.isOn:get()
return self._isOn
end
function ElectronicDevice.properties.isOn:set(value)
self._isOn = value
end
function ElectronicDevice:Reboot()
self._isOn = false
self:ResetHardware()
self._isOn = true
end
Router = Class:new(ElectronicDevice)
Modem = Class:new(ElectronicDevice)
function Modem:WarDialNeighborhood(areaCode)
local cisco = Router:new()
cisco:Reboot()
self:Reboot()
if self._isOn then
self:StartDialing(areaCode)
end
end
If you were to stick to get/set methods for properties, you wouldn't need __index
and __newindex
functions, and could just have an __index
table. 如果您坚持使用属性的get / set方法,则不需要
__index
和__newindex
函数,并且可能只有一个__index
表。 In that case, the easiest way to simulate inheritance is something like this: 在这种情况下,模拟继承的最简单方法是这样的:
BaseClass = {}
BaseClass.index = {}
BaseClass.metatable = {__index = BaseClass.index}
DerivedClass = {}
DerivedClass.index = setmetatable({}, {__index = BaseClass.index})
DerivedClass.metatable = {__index = DerivedClass.index}
In other words, the derived class's __index
table "inherits" the base class's __index
table. 换句话说,派生类的
__index
表“继承”基类的__index
表。 This works because Lua, when delegating to an __index
table, effectively repeats the lookup on it, so the __index
table's metamethods are invoked. 这是有效的,因为Lua在委托
__index
表时,会有效地重复查找,因此调用了__index
表的元方法。
Also, be wary about calling obj.Method(...)
vs obj:Method(...)
. 另外,要小心调用
obj.Method(...)
vs obj:Method(...)
。 obj:Method(...)
is syntactic sugar for obj.Method(obj, ...)
, and mixing up the two calls can produce unusual errors. obj:Method(...)
是obj.Method(obj, ...)
语法糖,混合两个调用会产生异常错误。
There are a number of ways you can do it but this is how I do (updated with a shot at inheritance): 有很多方法可以做到这一点,但这就是我的做法(更新了一下继承):
function newRGB(r, g, b)
local rgb={
red = r;
green = g;
blue = b;
setRed = function(self, r)
self.red = r;
end;
setGreen = function(self, g)
self.green= g;
end;
setBlue = function(self, b)
self.blue= b;
end;
show = function(self)
print("red=",self.red," blue=",self.blue," green=",self.green);
end;
}
return rgb;
end
purple = newRGB(128, 0, 128);
purple:show();
purple:setRed(180);
purple:show();
---// Does this count as inheritance?
function newNamedRGB(name, r, g, b)
local nrgb = newRGB(r, g, b);
nrgb.__index = nrgb; ---// who is self?
nrgb.setName = function(self, n)
self.name = n;
end;
nrgb.show = function(self)
print(name,": red=",self.red," blue=",self.blue," green=",self.green);
end;
return nrgb;
end
orange = newNamedRGB("orange", 180, 180, 0);
orange:show();
orange:setGreen(128);
orange:show();
I don't implement private, protected, etc. although it is possible . 虽然有可能 , 但我没有实现私有,受保护等。
The way I liked to do it was by implementing a clone() function. 我喜欢这样做的方法是实现clone()函数。
Note that this is for Lua 5.0. 请注意,这适用于Lua 5.0。 I think 5.1 has more built-in object oriented constructions.
我认为5.1有更多内置的面向对象的结构。
clone = function(object, ...)
local ret = {}
-- clone base class
if type(object)=="table" then
for k,v in pairs(object) do
if type(v) == "table" then
v = clone(v)
end
-- don't clone functions, just inherit them
if type(v) ~= "function" then
-- mix in other objects.
ret[k] = v
end
end
end
-- set metatable to object
setmetatable(ret, { __index = object })
-- mix in tables
for _,class in ipairs(arg) do
for k,v in pairs(class) do
if type(v) == "table" then
v = clone(v)
end
-- mix in v.
ret[k] = v
end
end
return ret
end
You then define a class as a table: 然后,您将类定义为表:
Thing = {
a = 1,
b = 2,
foo = function(self, x)
print("total = ", self.a + self.b + x)
end
}
To instantiate it or to derive from it, you use clone() and you can override things by passing them in another table (or tables) as mix-ins 要实例化它或从中派生它,你可以使用clone(),你可以通过将它们作为混合物传递到另一个表(或多个表)来覆盖它们
myThing = clone(Thing, { a = 5, b = 10 })
To call, you use the syntax : 要调用,请使用以下语法:
myThing:foo(100);
That will print: 这将打印:
total = 115
To derive a sub-class, you basically define another prototype object: 要派生子类,您基本上定义另一个原型对象:
BigThing = clone(Thing, {
-- and override stuff.
foo = function(self, x)
print("hello");
end
}
This method is REALLY simple, possibly too simple, but it worked well for my project. 这个方法非常简单,可能太简单了,但它对我的项目很有用。
It's really easy to do class-like OOP in Lua; 在Lua中完成类似OOP的操作非常简单; just put all the 'methods' in the
__index
field of a metatable: 只需将所有'方法'放在metatable的
__index
字段中:
local myClassMethods = {}
local my_mt = {__index=myClassMethods}
function myClassMethods:func1 (x, y)
-- Do anything
self.x = x + y
self.y = y - x
end
............
function myClass ()
return setmetatable ({x=0,y=0}, my_mt)
Personally, I've never needed inheritance, so the above is enough for me. 就个人而言,我从来不需要继承,所以上面对我来说已经足够了。 If it's not enough, you can set a metatable for the methods table:
如果还不够,可以为方法表设置元表:
local mySubClassMethods = setmetatable ({}, {__index=myClassMethods})
local my_mt = {__index=mySubClassMethods}
function mySubClassMethods:func2 (....)
-- Whatever
end
function mySubClass ()
return setmetatable ({....}, my_mt)
update: There's an error in your updated code: 更新:您更新的代码中存在错误:
Router = {};
mt_for_router = {__index=Router}
--Router inherits from ElectronicDevice
Router = setmetatable({},{__index=ElectronicDevice});
Note that you initialize Router
, and build mt_for_router
from this; 请注意,您初始化
Router
,并mt_for_router
构建mt_for_router
; but then you reassign Router
to a new table, while mt_for_router
still points to the original Router
. 但是你将
Router
重新分配给一个新表,而mt_for_router
仍然指向原来的Router
。
Replace the Router={}
with the Router = setmetatable({},{__index=ElectronicDevice})
(before the mt_for_router
initialization). 将
Router={}
替换为Router = setmetatable({},{__index=ElectronicDevice})
(在mt_for_router
初始化之前)。
Your updated code is wordy, but should work. 您更新的代码很冗长,但应该有效。 Except , you have a typo that is breaking one of the metatables:
除此之外 ,你有一个打破其中一个metatables的拼写错误:
--Modem inherits from ElectronicDevice Modem = setmetatable({},{__index,ElectronicDevice});
should read 应该读
--Modem inherits from ElectronicDevice Modem = setmetatable({},{__index=ElectronicDevice});
The existing fragment made the Modem
metatable be an array where the first element was almost certainly nil (the usual value of _G.__index
unless you are using strict.lua
or something similar) and the second element is ElectronicDevice
. 现有的片段使得
Modem
metatable成为一个数组,其中第一个元素几乎肯定是nil( _G.__index
的通常值,除非你使用strict.lua
或类似的东西),第二个元素是ElectronicDevice
。
The Lua Wiki description will make sense after you've grokked metatables a bit more. Lua Wiki描述在你更多地了解metatable之后会有意义。 One thing that helps is to build a little infrastructure to make the usual patterns easier to get right.
有一点有用的是建立一个小基础设施,使通常的模式更容易正确。
I'd also recommend reading the chapter on OOP in PiL . 我还建议阅读PiL中关于OOP的章节。 You will want to re-read the chapters on tables and metatables too.
您还需要重新阅读有关表格和元表的章节。 Also, I've linked to the online copy of the 1st edition, but owning a copy of the 2nd is highly recommended.
此外,我已经链接到第1版的在线副本,但强烈建议拥有第2版的副本。 There is also a couple of articles in the Lua Gems book that relate.
Lua Gems书中还有一些文章与之相关。 It, too, is recommended.
它也是推荐的。
Another simple approach for subclass 子类的另一种简单方法
local super = require("your base class")
local newclass = setmetatable( {}, {__index = super } )
local newclass_mt = { __index = newclass }
function newclass.new(...) -- constructor
local self = super.new(...)
return setmetatable( self, newclass_mt )
end
You still can use the functions from superclass even if overwritten 即使被覆盖,您仍然可以使用超类中的函数
function newclass:dostuff(...)
super.dostuff(self,...)
-- more code here --
end
don't forget to use ONE dot when pass the self to the superclass function 将self传递给超类函数时不要忘记使用一个点
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.