学过java或者C++的小伙伴肯定知道什么是继承,因为继承是面向对象的一大特征。面向过程的语言就不能实现继承了,lua也是一样。不过lua强大的table可以利用元表模拟出继承的功能。下面我就说下一如何用lua实现面向对象中继承的功能。
什么是继承?
曹操是三国时期杰出的政治家,文学家,书法家,军事家。总之这人很牛X!他的儿子们也都各有所长,为什么要说他的儿子们呢?因为:
曹丕:继承了曹操的政治和谋略
曹植:继承了曹操的天赋和才华
曹冲:继承了曹操的聪明和智慧
曹彰:继承了曹操的勇气和胆识
曹昂:被亲爹坑了
这种就叫继承。儿子们没有父亲的特征就没有继承。
理解lua查找元表的过程
举例说明:
Lua的表本质其实是个类似HashMap的东西,其元素是很多的Key-Value对,如果尝试访问了一个表中并不存在的元素时,就会触发Lua的一套查找机制,也是凭借这个机制来模拟了类似“继承”的行为
Caocao = {}
print(Caocao.Wife) --这里试图打印曹操的老婆,但是并没有。
执行结果:nil
输出为nil的原因很简单,Caocao中并没有Wife这个成员,这符合我们平时对HashMap的认知。但对于Lua表,如果Caocao有元表,情况就不同了。
举个栗子:
Caocao = {
kill = "吕布" --曹操杀了吕布
}
son = {
surname = "cao" --儿子们都姓曹
}
setmetatable(son, Caocao) --把son的metatable设置为Caocao
print(son.kill)
输出的结果是nil,儿子们没有杀吕布。但如果把代码改为
Caocao = {
kill = "吕布" --曹操杀了吕布
}
Caocao.__index = Caocao --把Caocao的__index方法指向自己
son = {
surname = "cao" --儿子们都姓曹
}
setmetatable(son, Caocao) --把son的metatable设置为Caocao
print(son.kill)
输出的结果为1,符合预期
这样一来,结合上例,来解释__index元方法的含义:
在上述例子中,访问son.kill时,son中没有kill这个成员。但Lua接着发现son有元表Caocao。
注意:这时候,Lua并不是直接在Caocao中找名为kill的成员,而是调用Caocao的__index方法,如果__index方法为nil,则返回nil,如果是一个表的__index方法等于自己,那么就到__index方法所指的这个表中查找名为kill的成员,于是,最终找到了kill成员。
注:__index方法除了可以是一个表,还可以是一个函数,如果是一个函数,__index方法被调用时将返回该函数的返回值。
到这里,总结一下Lua查找一个表元素时的规则,其实就是如下3个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表(操作指南),如果没有元表,返回nil,有元表则继续
3.判断元表(操作指南)中有没有关于索引失败的指南(即__index方法),如果没有(即__index方法为nil),则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值
理解了lua查找元表的过程,下面用实际的案例来进一步说明。
曹植对于曹操的继承案例
首先,我们建一个类,名称为Caocao.lua(曹操),定义两个属性分别为surname—姓,name—名,定义四个方法分别为wisdom—智慧,talent—才华,strategy—谋略,courage—胆识。
Caocao={} --初始化曹操
function Caocao:new() --相当于java中曹操的构造方法
local nc={
surname = "Cao", --姓
name = "cao", --名
}
setmetatable(nc,self) --元表,当子类调用其没有的属性或者方法时,会查找该元表的setmetatable,然后发现子类的元表为父类。(因为返回值赋给曹植,所以nc此时就是曹植,)
self.__index = self --接着发现父类有“_index”而且索引指向父类本身,所以子类在找不到属性或者方法时会来父类找。
return nc --返回给子类
end
function Caocao:wisdom() --智慧
print("曹操的智慧")
end
function Caocao:talent() --才华
print("曹操的才华")
end
function Caocao:strategy()--谋略
print("曹操的谋略")
end
function Caocao:courage() --胆识
print("曹操的胆识")
end
创建一个子类Caozhi.lua(曹植),定义一个属性:name—名,三个方法wisdom—智慧,strategy—谋略,courage—胆识。
为什么这样定义?因为曹植姓曹,且才华横溢,这些都是继承了曹操。这些用曹操的就行。下面看曹植的代码
require "Caocao" --导入曹操
Caozhi = Caocao:new() --初始化曹植
Caozhi.name = "zhi" -- 曹植的名
function Caozhi:wisdom() --没有智慧
print("")
end
function Caozhi:strategy()--没有谋略
print("")
end
function Caozhi:courage() --没有胆识
print("")
end
print(Caozhi.surname..Caozhi.name) --看看有没有继承曹操的姓并且用自己的名
Caozhi:talent() --继承的才华
Caozhi:courage() --胆识
打印结果:
结果符合预期。功能和面向对象中的一致,不同的地方在于lua是通过查找表元素实现的继承。就像我在Caocao.lua中注释的那样,曹植最开始就调用了曹操的构造方法,并将setmetatable中的第一个参数nc赋值给曹植,这样曹植的元表就是曹操了,同时曹操的元表又指向自己,所以曹植没有的属性或者方法会调用曹操的。