Lua实现类与继承的实例详解

作者:袖梨 2022-06-25


Lua中没有类的概念,但可以通过table、函数以及一些语言特性来模拟类。table的值可以为函数。看下面这段代码:

LogUtil = {msg_prefix = string.format("%s ", os.date("%H:%M:%S", os.time())) }
LogUtil.write = function(msg)
    print(LogUtil.msg_prefix .. msg .. "rn")
end
 
LogUtil.write("This is a Notice Info")

定义一个日志table,table有一个名为msg_prefix的键以及一个名为write的键。msg_prefix指向一个字符串,write指向一个方法,然后调用了write方法。这里看起来就是一个table的操作,但也可以换个角度来看:定义了一个日志类,类里有一个静态成员属性msg_prefix以及一个静态成员方法write。并且调用了write方法。

这也就是Lua实现类的基础。

上面代码中,LogUtil为全局变量。如果重写了它的值将会导致方法不可用。所以可以用一个额外的参数来传入对象,这个参数经常为self或this,好比Python类方法中也需要显示的传入self对象。如:

LogUtil = {msg_prefix = string.format("%s ", os.date("%H:%M:%S", os.time())) }
 
LogUtil.write = function(self, msg)
    print(self.msg_prefix .. msg .. "rn")
end
 
LogA = LogUtil
LogUtil = nil
LogA.write(LogA, "This is a LogA Info")

但这样子调用的时候也需要传入self参数,不过,Lua也提供了通过使用冒号操作符来隐藏这个参数的声明。看下面,

LogUtil = {msg_prefix = string.format("%s ", os.date("%H:%M:%S", os.time())) }
 
function LogUtil:write(msg)
    print(self.msg_prefix .. msg .. "rn")
end
 
LogA = LogUtil
LogA:write("This is a LogA Info")

类定义还是被调用,通过冒号方式的调用都会自动将类作为self参数传入,而省去了主动传入的麻烦。还有, function提在前面了,可以看到这里更有函数的味道了。上面两段代码是等价的,归纳下就是:

function class.a.b:c = function(param) body end

等价于

class.a.b.c = function(self, param) body end

可能,会被上面的点、冒号、self搞混,其实可以统一使用冒号的方式来定义与调用类方法。

到目前为止还只是一些属性和方法的集合,并没有实例化类,属性的改变会导致全局的变化。接下来看如何实现多个互不影响的类的实例。

类的实例

在Lua中有一个神奇的东西叫做metatable(元表)。metatable也是键值对。当访问table中一个不存在的属性时就会触发一些事件。比如当设置了元方法__index时 表示当调用当前table中不存在的属性或方法时会到元方法对应的table中去找,从而实现继承的效果。

setmetatable(a, {__index = b})

这样,对象a调用任何不存在的成员都会到对象b中查找。术语上,可以将b看作类,a看作对象。

LogUtil = {msg_prefix = string.format("%s ", os.date("%H:%M:%S", os.time())) }
 
function LogUtil:write(msg)
    print(self.msg_prefix .. msg .. "rn")
end
 
-- 创建实例化的new方法,名字可以任意制定。
function LogUtil:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end
 
LogA = LogUtil:new()
LogA.msg_prefix = ""
LogA:write("This is a LogA Info")
 
LogB = LogUtil:new{msg_prefix = "Hello."}
LogB:write("This is a LogB Info")
 
--[[
-- output:
This is a LogA Info
 
Hello.This is a LogB Info
]]

可以看到,A和B的操作是互不影响的,相当于对类进行了实例化。有两点需要注意的地方:
1、new方法中的setmetatable(o, self)只是做了一个小小的优化,不需要创建一个额外的表作为LogA或LogB的元表。再简单点说, 元表只是取了LogUtil的一个__index键。对于LogUtil来说,只是多了一个__index键而已。
2、LogB实例化的时候参数为一个table,此时可以省略括号。表示在o中已经设置了msg_prefix,不再需要去LogUtil寻找,达到重写的目的。

当执行LogA:write()时先在LogA中查找,LogA没有此方法,此时会到LogUtil中去找。基本上已经也有了继承的概念。

类的继承

在前面基础上我们调整下调用,LogA为LogUtil的实例,找不到的方法都到LogUtil中去找。如果想要LogB继承自LogA呢?可以直接在new一下LogA。

LogA = LogUtil:new{msg_prefix = 'A:'}
LogA:write("This is a LogA Info")
 
LogB = LogA:new{}
LogB:write("This is a LogB Info")

LogA再执行new方法时,此时的self参数为LogA了,所以相当于LogB继承自LogA。

同样, Lua还可以实现多重继承,成员属性私有等,这些就留待以后去深究了

相关文章

精彩推荐