概述
问题分析
分析这段代码和输出
local Gun = {}
-- 示例,实际应用还要考虑构造,虚表等情况
function LuaClass(Class, Parent
setmetatable(Class, {__index = Parent}
Class._Super = Parent
end
function Gun:Attack(
print("开始攻击";
self:Load(
self:Fire(
end
function Gun:Load(
print("装弹";
end
function Gun:Fire(
print("开枪";
end
Gun:Attack(;
local Cannon = {}
LuaClass(Cannon, Gun
function Cannon:Attack(
print("大炮开始攻击"
self._Super:Attack(
end
function Cannon:Fire(
print("开炮"
end
print("-------------------------------------"
Cannon:Attack(
输出:
红线圈出的地方虚函数调用错误,应该打印"开炮"。
使用元表来面向对象时,要注意__index元方法的语义:
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果
而
Gun:Attack( 等价于 Gun.Attack(self
self._Super:Attack( 等价于 Gun.(Gun 注意self._Super = Gun
所以调用父类Attack函数中,self的语义是Gun这张表,后面调用的就一直是Gun方法,所以***调用的是Gun的Fire,而不是Cannon的Fire。
解决方案
注意这种实现和C++的虚函数调用思路是不一样的
function LuaClass(Class, Parent
local FindVal = function(InClass, Key
local Raw = rawget(InClass, Key
if nil ~= Raw then
return Raw, InClass
end
if nil ~= InClass.__Base then
return FindVal(InClass.__Base, Key
end
end
Class.__Base = Parent
Class.__ClassPtr = Class
local Index = function(_, Key
local Val, ClassPtr = FindVal(Parent, Key
if nil == Val then
return
end
Class.__ClassPtr = ClassPtr
return Val
end
setmetatable(Class, {__index = Index}
local SuperIndex = function(_, Key
return function(_, ...
local OriClassPtr = Class.__ClassPtr
if nil == OriClassPtr.__Base then
return
end
local Val, ClassPtr = FindVal(OriClassPtr.__Base, Key
if nil == Val then
return
end
Class.__ClassPtr = ClassPtr
local Ret = {Val(Class, ...}
Class.__ClassPtr = OriClassPtr
return table.unpack(Ret
end
end
Class._Super = setmetatable({}, {__index = SuperIndex}
end
输出:
在__index元方法查询的时候,标记当前调用方法所在的表。
在_Super的元表__index元方法查询的时候,找到标记表的方法,使用Class表作为第一个参数self传入。
备注
支持虚函数有性能开销,可以在LuaClass加个参数控制是否支持虚函数。