oop - 如何在 Lua 中使用基类和子类构造函数模拟简单继承(教程)
问题描述
如何在 Lua 中使用父类和子类构造函数模拟简单继承?
如果您对此发表评论或将有趣的信息编辑到其中,我将提供并接受和回答,并将不胜感激。
解决方案
这是在 Lua 中模拟基本父/子类继承的指南。
我假设读者了解 Lua 的基本特性和语法。以下陈述尤其重要:
- Lua 中没有类或对象,只有表。
- 一个表可以有一个元表。
- 如果表的元
mt
表t
包含元__index
方法(因此),则尝试通过分配给的内容来解析对mt.__index
不存在的键 in的访问。t
mt.__index
- 如果
t
调用这样的函数t.foo()
,t:foo()
则t
其本身foo()
作为第一个参数被传递给调用self
。在函数内部,t
它本身可以作为self
.
基类
代码
Base = {}
function Base:new(name)
Base.__index = Base
local obj = {}
setmetatable(obj, Base)
obj.name = name
return obj
end
function Base:sayName()
print(self.name..": My name is "..self.name..".")
end
由于该函数的Base:new(name)
作用,Base
现在可以将其视为一个新类,并Base:new(name)
作为其构造函数。请记住,这Base
确实是一张位于内存某处的表格,而不是一些抽象的“类蓝图”。它包含一个函数Base:sayName()
。这就是我们可以称之为关于 OOP 术语的方法。
但是Base:new(name)
let如何表现Base
得像一个类?
解释
Base.__index = Base
该Base
表得到一个__index
元方法,即它自己。每当Base
用作元表时,对不存在索引的搜索将被重定向到...Base
本身。(等等。)该行也可以写为self.__index = self
, 因为Base
调用:new(name)
,因此self
在Base
其中。我更喜欢第一个版本,因为它清楚地显示了正在发生的事情。此外,Base.__index = Base
可以超出Base:new(name)
,但是,为了清楚起见,我更喜欢将所有“设置”都发生在一个范围内(“构造函数”)。
local obj = {}
setmetatable(obj, Base)
obj
被创建为一个新的空表。它将成为我们所认为的“类”的对象Base
。现在Base
是 的元表obj
。由于Base
具有__index
,对不存在的键的访问将obj
被重定向到分配给 的内容Base.__index
。由于Base.__index
isBase
本身,对 in 中不存在的键的访问obj
将被重定向到Base
(例如,它会找到的地方Base:sayName()
)!
obj.name = name
return obj
obj
(!) 获取一个新条目,一个member,构造函数参数被分配给该条目。然后obj
返回,我们将其解释为class 的对象Base
。
示范
b = Base:new("Mr. Base")
b:sayName()
这将打印“Mr. Base:我的名字是 Mr. Base”。正如预期的那样。如上所述,通过 metatable-机制b
找到,因为它没有这样的密钥。存在于内部(“类表”)和内部(“对象表”)。sayName()
__index
sayName()
Base
name
b
儿童班
代码
Child = {}
function Child:new(name, age) -- our child class takes a second argument
Child.__index = Child
setmetatable(Child, {__index = Base}) -- this is different!
local obj = Base:new(name, age) -- this is different!
setmetatable(obj, Child)
obj.age = age
return obj
end
function Child:sayAge()
print(self.name..": I am "..tonumber(self.age).." years old.")
end
代码几乎与基类的代码完全相同!Child:new(name, age)
在(即构造函数)中添加第二个参数并不是特别值得注意。Base
也可以有多个参数。但是,添加了里面的第二和第三行Child:new(name, age)
,这就是Child
“继承”的原因Base.
请注意,Base
可能包含Base.__index
,这使得它在用作元表时很有用,但它本身没有元表。
解释
setmetatable(Child, {__index = Base})
在这一行中,我们为类表分配了一个元Child
表。这个元表包含一个__index
元方法,它被设置为Base
类表。因此,Child
类表将尝试通过类表解析对不存在的键的访问Base
。因此,Child
类表可以访问它自己的所有方法和所有Base
的方法!
local obj = Base:new(name, age)
setmetatable(obj, Child)
在第一行中,Base
创建了一个新的对象表。此时,它的__index
元方法指向Base
类表。然而,就在第二行,它的元表被分配给Child
类表。obj
不会失去对类方法的访问权的原因Base
在于,我们之前将不成功的键访问权重定向Child
到Base
(通过提供Child
适当的元表)!由于obj
是作为Base
对象表创建的,因此它包含其所有成员。此外,它还包含Child
对象表的成员(一旦将它们添加到Child:new(name, age)
“构造函数”中。它Child
通过自己的元方法找到类表的方法。它在Base
类表通过Child
类表中的元方法。
注意: “Base
对象表”是指由Base:new(name)
. “Base
类表”是指实际的Base
表。请记住,Lua 中没有类/对象!类Base
表和Base
对象表一起模仿了我们认为的 OOP 行为。当然,这同样适用Child
。
Child
此外,在内部定义 ' 元表的赋值Child:new(name, age)
允许我们调用Base
' 的“构造函数”并将name
参数传递给它!
示范
c = Child:new("Mrs. Child", 42)
c:sayName()
c:sayAge()
这将打印“Mrs. Child:我的名字是 Mrs. Child”。和“Mrs. Child:我今年 42 岁。” 正如预期的那样。
结论
上面的部分描述了如何在 Lua 中实现 OOP 行为。了解这一点很重要
- 基本方法存在于
Base
类表中 - 基成员存在于
Base
由返回的对象表中Base:new()
- 子方法位于
Child
类表中 - 子成员位于由
Child
返回的对象表中Child:new()
引用正确的表是由表的元表完成的。
- 所有类表都将自己分配给它们的
__index
键。当用作元表时,它们引用自己(即类方法所在的位置)。每个“类”只有一个类表。 - 基类没有元表。它们被用作元表。
- 子类表也有一个元表,即
{__index = Base}
(将调用重定向到Base
类表,即Base
类方法所在的位置)。 - 所有对象表都将其对应的类表分配为元表。由于类表已经设置了它们的
__index
元方法,对对象表的调用可以重定向到类方法所在的类表。如果它是一个子类表(这意味着它也有一个元表),重定向可能会发生得更远。可以有任意多个对象表。
使用上图继续:如果Child
对象表c
尝试访问Base
方法会发生什么,例如c:sayName()
?嗯:有c
钥匙sayName()
吗?不,它有元表吗?是:(Child
班级Child
表)。Child
有元方法吗__index
?是的。它指向哪里?对Child
自己。Child
有sayName()
钥匙吗?没有Child
。有元表吗?是的。它有__index
元方法吗?是的。它指向哪里?到Base
班级表。它有sayName()
钥匙吗?是的!:-)
笔记
我不是 Lua 专家!到目前为止,我只在 Lua 中编写了一些脚本,但在过去的几天里,我试图把我的想法围绕在这个问题上。我发现了很多不同的、有时令人困惑的解决方案,最后得出了这个,我称之为最简单但透明的解决方案。如果您发现任何错误或警告,请不要犹豫发表评论!
推荐阅读
- javascript - 收到警告:过滤列表时超出最大更新深度
- ssh - 如何使用 MQTT.fx 客户端接收 Broker 的数据?
- java - 初学者java顺序递增设置变量并重置计数
- swift - 设备更改后,UIView 未调整宽度
- python - GridSearchCV - FitFailedWarning:估计器拟合失败
- c++ - 如何只显示有 5 个座位的汽车?
- python - 在 Python 中解析顶部行中的表/列和底部行中的记录的文本文件
- spring - Spring - 聚合 - 提要活动
- python - 将列表中的每个元素与另一个列表中的 2 个元素进行比较并提高代码效率
- javascript - Js - 将 scrollTop 转换为不透明度级别