Lua - 元表链式
链式元表是一种非常有效的技术,用于在 Lua 中实现类似继承的功能,并根据需要将操作委托给不同的 table 或对象。链式是通过使用 __index() 元方法实现的。
链式元表可以被视为 table 的回退机制。当我们尝试访问 table 中不存在的键时,Lua 会查找其元表中的 __index() 方法实现。如果 __index() 是另一个 table,那么 Lua 会继续在下一个 table 中搜索,并持续形成一个元表链来查找 __index() 方法实现。
__index 作为 table 的工作原理
考虑一个对象或 table 作为 table1,其中缺少某个键。
为该 table 设置一个元表 metatable1,即 setmetatable(table1, metatable1)。
metatable1 有一个 __index 字段指向另一个 table,即 table2。
现在 Lua 首先由于 __index 字段而在 table2 中查找缺失的键。如果找到键,则返回 table2 中的值。
如果键未找到且 table2 又有一个 metatable2,其 __index 设置为 table3,则 Lua 会继续在 table3 中搜索,依此类推。
示例 - 使用 __index 作为 table 的链式元表
让我们创建一个 Animal → Dog → Pug 的继承链。
main.lua
-- 基类:Animal
Animal = { sound = "Generic Roar" }
Animal.__index = Animal
-- 方法 makeSound
function Animal:makeSound()
print(self.sound)
end
-- 子类:Dog,继承自 Animal 类
Dog = setmetatable({ breed = "Bulldog" }, { __index = Animal })
Dog.__index = Dog
-- Dog 类特有的方法
function Dog:bark()
print("Woof!")
end
-- 重写父类方法
function Dog:makeSound()
print("Woof woof!")
end
-- 子类:Pug,继承自 Dog 类
Pug = setmetatable({ name = "PUG" }, { __index = Dog })
-- 用于 : 语法调用方法时。
Pug.__index = Pug
-- Pug 类特有的方法
function Pug:snore()
print("Zzz..")
end
-- 创建 Animal 实例
local animal = {}
setmetatable(animal, { __index = Animal })
-- 创建 Dog 实例
local dog = {}
setmetatable(dog, { __index = Dog })
-- 创建 Pug 实例
local pug = {}
setmetatable(pug, { __index = Pug })
-- 链式过程
animal:makeSound() -- Generic Roar
dog:makeSound() -- Woof woof! (重写)
dog:bark() -- Woof! (继承)
print(dog.sound) -- Generic Roar (继承)
print(dog.breed) -- Bulldog (自身属性)
pug:makeSound() -- Woof woof! (从 Dog 继承)
pug:bark() -- Woof! (从 Dog 继承)
pug:snore() -- Zzz.. (自身方法)
print(pug.sound) -- Generic Roar (从 Animal 继承)
print(pug.breed) -- Bulldog (从 Dog 继承)
print(pug.name) -- PUG (自身属性)
输出
运行上述程序时,我们将得到以下输出−
Generic Roar Woof woof! Woof! Generic Roar Bulldog Woof woof! Woof! Zzz.. Generic Roar Bulldog PUG
__index 作为函数的工作原理
我们也可以将 __index 元方法用作函数。这个函数将返回 table,并且现在它将以先前解释的相同方式工作,
main.lua
-- Table 1
Table1 = { value1 = "Hello" }
-- Table 2
Table2 = { value2 = "World" }
setmetatable(Table1, { __index = Table2 })
-- 创建实例
local instance = {}
setmetatable(instance, {
__index = function(table, key)
print("正在检查缺失的键 '" .. key .. "'") -- 翻译注释
return Table1[key]
end
})
-- 打印:正在检查缺失的键 'value1' \n Hello
print(instance.value1)
-- 打印:正在检查缺失的键 'value2' \n World
print(instance.value2)
-- 打印:正在检查缺失的键 value3 \n nil
print(instance.value3)
输出
运行上述程序时,我们将得到以下输出−
Checking for missing key 'value1' Hello Checking for missing key 'value2' World Checking for missing key 'value3' nil
链式 Metatables 的优势
继承 − 我们可以创建子类的层次结构,从超类继承属性。
可重用性 − 可以在基类中定义通用代码,然后由子类重用。
委托 − 可以将某些操作委托给专用的对象,例如日志记录。
组织 − 可以通过定义 table,并将它们与专用的 table 链式连接来组织代码,等等。