浅谈lua之实现面向对象

众所周知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")

 

你可能感兴趣的:(lua,lua,面向对象编程)