如何在 Lua 中使用基类和子类构造函数来模拟简单继承(教程)

LCs*_*Csa 0 oop inheritance lua

如何在Lua中使用父类和子类构造函数来模拟简单继承?

我将提供、接受和回答,如果您对此发表评论或编辑有趣的信息,我将不胜感激。

LCs*_*Csa 5

这是在 Lua 中模仿基本父/子类继承的指南。


我假设读者已经了解 Lua 的基本特性和语法。以下声明尤其重要:

  • Lua 中没有类或对象,只有表。
  • 一个表可以有一个元表。
  • 如果mt表的元表t包含__index元方法(因此),则尝试通过分配给 的内容来解析mt.__index对 in 中不存在的键的访问。tmt.__index
  • 如果调用这样的t函数,则其本身将作为第一个参数传递给名为 的函数。在函数内部,它本身可以作为 访问。t.foo()t:foo()tfoo()selftself

基类

代码

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
Run Code Online (Sandbox Code Playgroud)

由于该函数的作用Base:new(name)Base现在可以将其视为一个新类,并Base:new(name)作为其构造函数。请记住,这Base实际上是一个位于内存中某处的表,而不是一些抽象的“类蓝图”。它包含一个函数Base:sayName()。这就是我们可以用 OOP 术语来称呼的方法。

Base:new(name)但是let如何Base表现得像一个类呢?

解释

Base.__index = Base
Run Code Online (Sandbox Code Playgroud)

Base表有一个__index元方法,即它本身。每当Base用作元表时,对不存在索引的搜索将被重定向到...Base本身。(等等。)该行也可以写为self.__index = self,因为Base调用:new(name)且 因此self也在Base其中。我更喜欢第一个版本,因为它清楚地显示了正在发生的事情。另外,Base.__index = Base可以超出Base:new(name),但是,为了清楚起见,我更喜欢让所有“设置”发生在一个范围(“构造函数”)内。

local obj = {}
setmetatable(obj, Base)
Run Code Online (Sandbox Code Playgroud)

obj创建为一个新的空表。它将成为我们所认为的“类”的对象Base。现在是 的Base元表obj。由于Base__index,对不存在的键的访问将obj被重定向到分配给 的内容Base.__index。由于Base.__indexisBase本身,对 in 中不存在的键的访问obj将被重定向到(例如,Base它会找到 的地方)!Base:sayName()

obj.name = name
return obj
Run Code Online (Sandbox Code Playgroud)

( obj!) 获取一个新条目,即成员,构造函数参数被分配给该成员。然后返回obj,我们将其解释为class 的对象Base

示范

b = Base:new("Mr. Base")
b:sayName()
Run Code Online (Sandbox Code Playgroud)

这会打印出“Mr. Base:我的名字是 Mr. Base”。正如预期的那样。通过如上所述的元表机制b查找,因为它没有这样的密钥。存在于内部(“类表”)和内部(“对象表”)。sayName()__indexsayName()Basenameb

儿童班

代码

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
Run Code Online (Sandbox Code Playgroud)

该代码几乎与基类完全相同!在(即构造函数)中添加第二个参数Child:new(name, age)并不是特别值得注意。Base也可以有多个参数。Child:new(name, age)然而,添加了里面的第二行和第三行,这就是导致Child继承”的原因Base.

请注意,Base可能包含Base.__index,这使得它在用作元表时很有用但它本身没有元表。

解释

setmetatable(Child, {__index = Base})
Run Code Online (Sandbox Code Playgroud)

在这一行中,我们将元表分配给Child类表。该元表包含一个__index元方法,该元方法设置为Base类表。因此,Child类表将尝试通过类表解决对不存在键的访问Base。因此,Child类表可以访问它自己的所有方法和 的所有Base方法!

local obj = Base:new(name, age)
setmetatable(obj, Child)
Run Code Online (Sandbox Code Playgroud)

在第一行中,Base创建了一个新的对象表。此时,它的__index元方法指向Base类表。然而,在第二行,它的元表被分配给Child类表。之所以obj不会失去对Base类方法的访问权限,是因为我们之前将不成功的键访问重定向ChildBase(通过提供正确的元表)!Child由于obj是作为Base对象表创建的,因此它包含其所有成员。此外,它还包含对象表的成员Child(一旦它们被添加到“构造函数”中。它通过自己的元方法Child:new(name, age)查找类表的方法。并通过类中的元方法查找类表中的方法桌子。ChildBaseChild

注意:Base对象表”是指由 . 返回的表Base:new(name)。“Base类表”是指实际的Base表。请记住,Lua 中没有类/对象!类Base表和Base对象表一起模仿了我们所认为的 OOP 行为。Child当然,这同样适用。

Child此外,确定's 元表内部赋值的范围Child:new(name, age)允许我们调用Base's “构造函数”并将name参数传递给它!

示范

c = Child:new("Mrs. Child", 42)
c:sayName()
c:sayAge()
Run Code Online (Sandbox Code Playgroud)

这将打印“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 编写了一些脚本,但在过去的几天里,我试图集中精力解决这个问题。我发现了很多不同的、有时令人困惑的解决方案,最后得出了这个,我称之为最简单但透明的解决方案。如果您发现任何错误或警告,请随时发表评论!