众所周知lua并没有面向对象的概念,但我们可以用lua的一些特性(元表、元方法)近似模拟出一些面向对象的特征:类、继承、多态
1.类
我们可以用table来模拟一个类,参见如下:
--基类
Base = {x, y}
--创建一个基类的实例
function Base:New(x, y)
local o = {}
setmetatable(o, self)
self.__index = self
o.x = x or 0
o.y = y or 0
return o
end
function Base:PrintInfo()
print("this is base class","x=", self.x, ",y=", self.y)
end
类的实例化,参见如下:
--实例化一个基类
baseInstance = Base:New(6, 9)
baseInstance:PrintInfo()
输出如下:
this is base class x= 6 ,y= 9
这里Base是基类,baseInstance是基类的实例;Base:New用来创建一个基类的实例,并设置了实例类baseInstance和Base类之间的关联(元表)
注意两点:
1)实例方法用冒号: 来调用,这样方法中可以访问一个隐藏的表变量self;例:Base:New(6, 9)
2)x、y赋值时,这里注意用o.x和o.y,而不是self.x和self.y;因为当为后者时,所有New出来实例的x、y值都是共享的,这显然没有达到New的效果
2.继承
--派生类
Child = Base:New()
--重写派生类的实例化方法
function Child:New(x, y, z)
local o = Base:New(x, y)
setmetatable(o, self)
self.__index = self
o.z = z or 0
return o
end
--重写基类方法
function Child:PrintInfo()
print("this is child class","x=", self.x, ",y=", self.y, ",z=", self.z)
end
这里Child是Base的派生类,并且重写了基类的New和PrintInfo方法,参见如下实例调用:
--实例化一个派生类
childInstance = Child:New(10, 20, 30)
childInstance:PrintInfo()
输出如下:
this is child class x= 10 ,y= 20 ,z= 30
这里Child:New重写基类方法的同时,还为自己的变量z做了赋值
3.多态
多态包含:重写和重载,这里我们分开讨论
1)重写
以上示例中Child:PrintInfo()即为对Base:PrintInfo()的一个重写,这里从如下输出也能看出:
this is base class x= 6 ,y= 9
this is child class x= 10 ,y= 20 ,z= 30
可以看出用起来还是比较简单的,但是如果我们想在派生类中访问基类被重写的方法怎么办呢? 参见如下:
function Base:Debug()
print("this is base class")
end
--重写基类方法
function Child:Debug()
self:GetBase():Debug()
print("this is child class")
end
--获取基类
function Child:GetBase()
return getmetatable(Child)
end
childInstance = Child:New(10, 20, 30)
childInstance:Debug()
print("--------------------------------------")
childInstance:GetBase():Debug()
输出如下:
this is base class
this is child class
--------------------------------------
this is base class
可以看到我们只需要在派生类Child中定义一个获取基类的方法即可Child:GetBase()
这里childInstance:Debug()会先访问基类的Base:Debug(),然后再调用自身的输出;而childInstance:GetBase():Debug(),则是用派生类实例直接调用基类的方法
注意:这里子类调用基类被重写的方法内如果涉及变量的赋值要怎么写呢? 参见如下:
function Base:SetInfo(x, y, obj)
obj = obj or self
obj.x = x or 0
obj.y = y or 0
end
--重写基类方法(带参/访问内部参数)
function Child:SetInfo(x, y, z, obj)
obj = obj or self
obj:GetBase():SetInfo(x, y, obj)
obj.z = z or 0
end
--实例化一个基类
baseInstance = Base:New(6, 9)
baseInstance:PrintInfo()
--实例化一个派生类
childInstance = Child:New(10, 20, 30)
childInstance:PrintInfo()
print("------------- < after set value > -------------")
baseInstance:SetInfo(8, 5)
baseInstance:PrintInfo()
childInstance:SetInfo(111, 222, 333)
childInstance:PrintInfo()
输出如下:
this is base class x= 6 ,y= 9
this is child class x= 10 ,y= 20 ,z= 30
------------- < after set value > -------------
this is base class x= 8 ,y= 5
this is child class x= 111 ,y= 222 ,z= 333
注意:这里有一个坑:在子类调用基类方法时,obj一定要传子类的实例对象,如下:
function Child:SetInfo(x, y, z, obj)
obj = obj or self
obj:GetBase():SetInfo(x, y, obj)
obj.z = z or 0
end
子类实例调用:
childInstance:GetBase():SetInfo(666, 123, childInstance)
childInstance:PrintInfo()
this is child class x= 666 ,y= 123 ,z= 333
-------------------------------------------------------- 以下为错误调用示范 --------------------------------------------------------
如果调用时漏传了childInstance,例如:
childInstance:GetBase():SetInfo(666, 123)
childInstance:PrintInfo()
print("Base.x=", Base.x, ", Base.y=", Base.y)
则输出如下:
this is child class x= 111 ,y= 222 ,z= 333
Base.x= 666 , Base.y= 123
可以看到子类实例childInstance的x、y没有被改掉,反而基类Base的x、y被改掉了,这是个大坑,一定要注意!!!
--------------------------------------------------------------------------------------------------------------------------------------
2)重载
function Base:Text()
print("this is base text")
end
function Base:Text(parm)
print("this is base text : ", parm)
end
childInstance:Text()
输出如下:
this is base text : nil
这里可以看到调用的虽然是无参方法Text(),但实际执行的是有参的方法Text(parm);这是因为lua本身不支持重载,内部调用猜测是依照方法注册的顺序调用第一个,所以重载只能用类似扩展的模式,自己写不同的方法来调用,如下:
--重载方法
function Base:Text()
print("this is base text")
end
--重载方法
function Base:TextParm(parm)
print("this is base text : ", parm)
end
childInstance:Text()
childInstance:TextParm("input parm")
输出如下:
this is base text
this is base text : input parm
最后附上完整的源码:
--基类
Base = {x, y}
--创建一个基类的实例
function Base:New(x, y)
local o = {}
setmetatable(o, self)
self.__index = self
o.x = x or 0
o.y = y or 0
return o
end
function Base:PrintInfo()
print("this is base class","x=", self.x, ",y=", self.y)
end
function Base:Debug()
print("this is base class")
end
function Base:SetInfo(x, y, obj)
obj = obj or self
obj.x = x or 0
obj.y = y or 0
end
--重载方法
function Base:Text()
print("this is base text")
end
--重载方法
function Base:TextParm(parm)
print("this is base text : ", parm)
end
--派生类
Child = Base:New()
--重写派生类的实例化方法
function Child:New(x, y, z)
local o = Base:New(x, y)
setmetatable(o, self)
self.__index = self
o.z = z or 0
return o
end
--获取基类
function Child:GetBase()
return getmetatable(Child)
end
--重写基类方法
function Child:PrintInfo()
print("this is child class","x=", self.x, ",y=", self.y, ",z=", self.z)
end
--重写基类方法
function Child:Debug()
self:GetBase():Debug()
print("this is child class")
end
--重写基类方法(带参/访问内部参数)
function Child:SetInfo(x, y, z, obj)
obj = obj or self
obj:GetBase():SetInfo(x, y, obj)
obj.z = z or 0
end
--示例
--实例化一个基类
baseInstance = Base:New(6, 9)
baseInstance:PrintInfo()
--实例化一个派生类
childInstance = Child:New(10, 20, 30)
childInstance:PrintInfo()
print("------------- < after set value > -------------")
baseInstance:SetInfo(8, 5)
baseInstance:PrintInfo()
childInstance:SetInfo(111, 222, 333)
childInstance:PrintInfo()
print("-----------------------------------------------")
childInstance:GetBase():SetInfo(666, 123)
childInstance:PrintInfo()
print("Base.x=", Base.x, ", Base.y=", Base.y)
print("-----------------------------------------------")
childInstance:Debug()
print("-----------------------------------------------")
childInstance:GetBase():Debug()
print("-----------------------------------------------")
childInstance:Text()
childInstance:TextParm("input parm")