貌似是第一次发帖,以下内容是从我的博客抄过来的,原文地址:http://jennal.com/2014/10/18/cocos2dx-lua-oop/
原文格式会更清楚一点,搬过来实在懒得重新编辑一遍。。好像第一次发帖就有点踢馆的意思,其实没有,只是把问题想得深入一点,只是抛砖引玉,希望可以引起大家的重视和讨论。
起因
网路上已经有很多关于lua面向对象开发的文章,为什么我要自己写一篇呢?
一切都是因cocos2d-x 3.2的lua绑定而起。我们的新项目打算用lua进行开发,一直在跟进quick-x项目,从独立、第三方,到现在的被触控收编。cocos2d-x 3.2是最新的稳定版,把quick-x的很多好东西吸纳进来。与其说吸纳,不如说是直接把源代码拿过来了。所以,现在cocos2d-x 3.2直接就支持lua的面向对象开发,提供了原来只有quick-x才有的class等方法来方便的创建对象以及做继承。
既然cocos2d-x官方都认为quick-x的东西很好,我也不得不研究一下这套针对lua开发的创建类和继承的方法。不研究不要紧,一研究吓一跳。备受关注的quick-x,把自己宣传的那么好,那么便捷(我刚开始研究quick-x的时候,用它写过一个项目,提供的接口确实很便捷,大大提高了开发效率,但是没有研究实现细节),没想到最最底层,最最基础的部分,创建类、类继承的方案却如此业余,简直是一个刚学lua两三天的“高手”写出来的。说刚学两三天,是因为,从这个方案可以看出,完全没有理解lua语言的真谛,没有做到thinking in lua,而“高手”并不是贬义,一般人真的做不出这样的设计,写不出这样的代码。
分析
当然,也不是说这份代码一无是处(下个章节会详细分析),我这里先说说这份代码的优点和缺陷,仅代表个人意见,希望能引起共鸣。
优点
[list=a]
分解了创建实例(cls.__create)和构造函数的方法(instance:ctor),这样做的好处很明显,让混沌的思路清晰起来,并且“支持”从function继承,这也是支持从cpp对象继承的基础。
创造了类与类的实例之间的区别
[list]
[li]类:cls.class == nil[/li]
[li]类的实例:instance.class ~= nil[/li]
[/list]
支持从cpp、lua的table继承,也支持创造新的类
支持默认构造函数
当创建新的类时,有一个什么都不做的构造函数
当lua的table继承时,可以调用父类的构造函数
缺陷
[list=a]
不支持多继承,这对于从cpp开发转过来的开发人员来说,可能是最大的问题,因为lua的特性其实是可以支持多继承的
没有充分利用lua的特性(下面review的时候详细展开讲)
从lua的table继承时,不需要做clone,clone会失去很多好处,并且带来额外的内存开销
对于不是使用class创建的父类,支持不好,这个其实问题不大,就是子类必须要写构造函数ctor
小结
所以总体上来看,还是优点多于缺点的。但是对于用惯了多继承(即使是不支持多继承的语言,比如Java、C#也是支持多接口实现的)的我来说,这点实在不可忍,所以不得不自己写了一套继承机制,为了避免这篇文章太长,将在下一篇文章中公开。
代码review
下面是重头戏了,让我们来看看class函数的代码。
[code]–Create an class.
function class(classname, super)
– Part 1 –
local superType = type(super)
local cls
if superType ~= "function" and superType ~= "table" then
superType = nil
super = nil
end
– End of Part 1 –
if superType == "function" or (super and super.__ctype == 1) then
– Part 2 –
– inherited from native C++ Object
cls = {}
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
end
cls.ctor = function() end
cls.__cname = classname
cls.__ctype = 1
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
– End of Part 2 –
else
– Part 3 –
– inherited from Lua Object
if super then
cls = clone(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 of Part 3 –
end
return cls
end[/code]
为了让没有使用过这个函数的读者也能读懂,先说一下函数的用法吧
[code]— Sample1: 创建新的类
– 创建一个名为ClassA的类
local ClassA = class("ClassA")
– 创建ClassA类的实例
local a = ClassA.new()
– ClassA的构造函数
function ClassA:ctor()
self.name = "a"
end
local aa = ClassA.new()
print(aa.name) – 输出:a
— Sample2: 从lua的table继承
– 创建一个名为ClassB的类,ClassB继承于ClassA
local ClassB = class("ClassB", ClassA)
– 创建ClassB类的实例
local b = ClassB.new()
— Sample3: 从cpp对象(在lua中是userdata)继承
– 创建一个名为ClassC的类,ClassC继承于cc.Scene
local ClassC = class("ClassC", function()
return cc.Scene:create()
end)
– 创建ClassC类的实例
local c = ClassC.new()[/code]
总体上看,这个函数可以分为3个部分(我已经在代码中用注释标明)。
局部变量声明以及参数类型检查
从cpp类继承
从lua的table继承
局部变量声明以及参数类型检查
[code]local superType = type(super) – 父类的类型,用于后面决定要进入哪个分支
local cls – 这将是最后的返回值
– 父类的类型必须是function或者table,如果都不是,就当做super传的是nil,也就是说当做创建新类来处理
if superType ~= "function" and superType ~= "table" then
superType = nil
super = nil
end[/code]
从cpp类继承
[code]-- inherited from native C++ Object
cls = {} – 初始化cls
– 父类是从cpp的类继承的
if superType == "table" then
– 从父类拷贝所有的元素,我认为这里是有问题的,详见下面的问题1
for k,v in pairs(super) do cls[k] = v end
cls.__create = super.__create – 我想这个只是用来提醒自己,其实这个赋值已经包含在上面的for循环中了
cls.super = super
else
– 传进来的super是function,__create用来创建类的实例
cls.__create = super
end
cls.ctor = function() end – 默认构造函数,我认为这里有2个问题的,详见下面的问题2和问题3
cls.__cname = classname – 类名
cls.__ctype = 1 – 类的类型,代表是从cpp的类(userdata)继承的
– 类的new方法,同问题3
function cls.new(…)
local instance = cls.__create(…) – 创建类的实例
– 拷贝所有的元素到实例中,我认为这里也是有问题的,详见下面的问题4
for k,v in pairs(cls) do instance[k] = v end
instance.class = cls – 对实例的class进行赋值,可以从实例找到cls
instance:ctor(…) – 调用构造函数
return instance
end[/code]
问题1:从父类做深度拷贝
这里的super已经是拷贝过cpp类(userdata)的所有元素,已经是个lua的table了,我认为这里可以完全当做从lua的table继承来处理,而没必要再做一次深度拷贝。
问题2:默认的空构造函数
先理一下代码走到这里的前提
super是function
或者super是从cpp类继承的table
你想到了什么?对,如果super是从cpp类继承的table,那么这里的默认构造函数就屏蔽了父类的构造函数。当然,在构造函数ctor里面有办法调到父类的构造函数的,可以这么做self.class.super.ctor(self)。但是回过头来看第一个前提,super是function的时候,super是没有被赋值到self.class.super的,所以现在要调用父类的构造函数的话,莫非还要判断一下?
问题3:不能被重复利用的代码
由于lua的随意性,很容易同一份代码被“写了多次”,又由于lua的函数是第一类对象,也就是说函数是可以被动态创建的。那么所有代码中,被我标记了问题3的地方,这些地方的函数,虽然看起来就一份代码,但是被创建出来以后却全是不同的实例。试想一下,每一个新创建的类型(不是类的实例),都包含了一个冗余的代码块,得有多大的代价?(实际开销可能并不大,但总觉得是个浪费。)
问题4:创建实例时的深度拷贝
这是在干嘛呢?拷贝那么多遍干嘛?都已经拷贝一遍到cls里面了,再拷贝一遍到instance干嘛呢?完全没有thinking in lua嘛!这里只需要setmetatable就可以了呀。
从lua的table继承
[code]
– inherited from Lua Object
if super then
cls = clone(super) – 从父类做深度拷贝,又来了,问题5
cls.super = super – 标记父类
else
cls = {ctor = function() end} – 父类为空时,创建默认构造函数,同上面的问题3
end
cls.__cname = classname – 保存类名
cls.__ctype = 2 – lua – 标记这是个lua继承来的类
cls.__index = cls – 为setmetatable做准备[/code]
– 类的new方法,同上面的问题3
function cls.new(...)
local instance = setmetatable({}, cls) -- “正统的”lua类继承来了
instance.class = cls -- 对实例的class进行赋值,可以从实例找到cls
instance:ctor(...) -- 调用构造函数
return instance
end
问题5:从父类做深度拷贝
刚刚从userdata做深度还是可以理解的,但是现在是从lua的table做继承呀,放着setmetatable不用,又clone干嘛呀?
总结
很多观点可能会有人认为偏颇,因为大多是深度拷贝的问题,而深度拷贝的时候,对于table、function、userdata等数据来说,也只是拷贝一个引用,并没有太大的代价。但我始终固执地认为,既然要用lua,就应该用lua的思维来解决问题,特别是底层的部分,而且底层的部分,对于内存和执行效率的考虑应该更深入,更全面一些。
不过至少大家可以达成共识,最大的问题就是问题2。
最后,放出我自己写的Oop库来解决上面的问题,并且有更多惊喜等着你去发现。