众所周知,lua在语法层面是不提供class关键字的,lua要实现面向对象编程(OOP)里面的继承,通常要用table和元表来实现。包括官方教程和网络上都有很多关于如何用lua实现面向对象编程的介绍。因为我觉得class的实现已经比较完善了,所以这篇文章主要介绍是介绍别人的class实现,再加上自己的一些点评,没有太多原创,好了,不废话,开始。
首先说一下面向对象编程(OOP)的三大特性:封装,继承,多态。
一、封装就是对数据成员的封装,对Lua而言,我理解的封装应该是这样的:table类型变量内,某些成员能让外界访问,某些成员不能让外界访问。封装功能在Lua实现是比较麻烦的,《Programming in Lua》一书里面提到了几种可以实现私有访问的方法:
1使用闭包,像下面例子的代码newAccout返回的table,只能通过函数访问balance,外部不能直接访问balance。
function newAccount ()
local self = {
balance =0
}
local getBalance = function ()
return self.balance
end
local setBalance = function (A)
self.balance=A
end
return {getBalancegetBalance,setBalance=setBalance}
end
2Package,就是setfenv函数,改变当前的函数环境,来实现某些变量不可见,具体可以参考lua 5.1后增加的module函数。
3__index,使用元表,将私有值放入元表中,通过__index来控制。
但是要做到像c++那样方便用public和private就能实现table成员的可见和不过见,基本不太可能。因为 Lua本身就是个脚本语言,讲究的是灵活性,封装成员已经偏离它的初衷了,所以,在封装这个问题上,我完全放弃考虑。
二、再来说多态,用过c++的就都知道用父类的指针调用一个虚函数,这个函数最后调的是父类的还是子类的,是不知道的,完全取决于这个指针在运行时指向了什么类型的数据。由于Lua的变量类型都是动态的,没有指针这个概念,所有的table变量都算是引用,通过 table调用它的一个成员函数,最后调用了它本身的,还是元表的,完全是在运行时决定,就这个角度来说,Lua天生就是具有多态的,不需要再实现。
三、而继承,这是Lua实现OOP的主要着力点,也是我们实现OOP的主要目的。哈哈,面向对象编程这种思想,在我来看,其实就是为了少写点代码,让子类可以复用父类的代码,本质就是一个懒字。
所以,用Lua实现OOP最重要的就是能实现继承,继承,继承,重要的事情要说三次。
下面开始简单说怎么用Lua实现继承,先说一下《Programming in Lua》中实现类的方法:
Account = {balance = 0}
function Account:new (o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
return o
end
function Account:deposit (v)
self.balance = self.balance + v
end
function Account:withdraw (v)
if v > self.balance then error"insufficient funds" end
self.balance = self.balance - v
end
上 述代码中new返回的table变量能够通过__index获得元表Accout的所有成员,Accout就相当于是类的原型,new函数返回值是类的实现。
《Programming in Lua》中实现类继承的方法,下述的SpecialAccount拥有了Account的deposit方法,并且重定义了withdraw方法:
SpecialAccount = Account:new()
function SpecialAccount:withdraw (v)
if v - self.balance >= self:getLimit() then
error"insufficient funds"
end
self.balance = self.balance - v
end
接着我要说一下cocos2dx里面是怎么实现类的:
function class(classname, super)
local superType = type(super)
local cls
--如果父类既不是函数也不是table则说明父类为空
if superType ~= "function" and superType ~= "table" then
superType = nil
super = nil
end
--如果父类的类型是函数或者是C对象
if superType == "function" or (super and super.__ctype == 1) then
-- inherited from native C++ Object
cls = {}
--如果父类是表则复制成员并且设置这个类的继承信息
--如果是函数类型则设置构造方法并且设置ctor函数
if superType == "table" then
-- copy fields from super
for k,v in pairs(super) do cls[k] = v end
cls.__create = super.__create
cls.super = super
else
cls.__create = super
cls.ctor = function() end
end
--设置类型的名称
cls.__cname = classname
cls.__ctype = 1
--定义该类型的创建实例的函数为基类的构造函数后复制到子类实例
--并且调用子数的ctor方法
function cls.new(...)
local instance = cls.__create(...)
-- copy fields from class to native object
for k,v in pairs(cls) do instance[k] = v end
instance.class = cls
instance:ctor(...)
return instance
end
else
--如果是继承自普通的lua表,则设置一下原型,并且构造实例后也会调用ctor方法
-- inherited from Lua Object
if super then
cls = {}
setmetatable(cls, {__index = super})
cls.super = super
else
cls = {ctor = function() end}
end
cls.__cname = classname
cls.__ctype = 2 -- lua
cls.__index = cls
function cls.new(...)
local instance = setmetatable({}, cls)
instance.class = cls
instance:ctor(...)
return instance
end
end
return cls
end
上面是cocos class的实现代码,它和《Programming in Lua》继承实现最大的不同点是,class提供了两种new的实现。其中一种实现就是支持对userdata的扩展,new出来的东西可以是一个userdata,但是将class的函数复制到了userdata上面,使得userdata也拥有了class的成员函数。
上述lua 的class实现是一种比较成熟的方案,可以实现简单的lua对象继承,也可以实现对cocos对象的扩展,还支持多层继承,但是它也有一个明确的缺点,没有实现多基类继承。在下一篇文章中,我将会说明怎么实现多基类继承。