« 网络游戏中的货币系统 | 返回首页 | 概率游戏 »

在 Lua 中实现面向对象

在 pil 中,lua 的作者推荐了一种方案来实现 OO,比较简洁,但是我依然觉得有些繁琐。

这里给出一种更漂亮一点的解决方案:为了贴代码和修改方便,我把它贴在了 wiki 上。

Lua 中实现面向对象

在这个方案中,只定义了一个函数 class(super) ,用这个函数,我们就可以方便的在 lua 中定义类:

base_type=class()       -- 定义一个基类 base_type

function base_type:ctor(x)  -- 定义 base_type 的构造函数
    print("base_type ctor")
    self.x=x
end

function base_type:print_x()    -- 定义一个成员函数 base_type:print_x
    print(self.x)
end

function base_type:hello()  -- 定义另一个成员函数 base_type:hello
    print("hello base_type")
end

以上是基本的 class 定义的语法,完全兼容 lua 的编程习惯。我增加了一个叫做 ctor 的词,作为构造函数的名字。

下面看看怎样继承: test=class(basetype) -- 定义一个类 test 继承于 basetype

function test:ctor()    -- 定义 test 的构造函数
    print("test ctor")
end

function test:hello()   -- 重载 base_type:hello 为 test:hello
    print("hello test")
end

现在可以试一下了:

a=test.new(1)   -- 输出两行,base_type ctor 和 test ctor 。这个对象被正确的构造了。
a:print_x() -- 输出 1 ,这个是基类 base_type 中的成员函数。
a:hello()   -- 输出 hello test ,这个函数被重载了。

其实,实现多重继承也并不复杂,这里就不再展开了。更有意义的扩展可能是增加一个 dtor :)

ps. 这里用了点小技巧,将 self 绑定到 closure 上,所以并不使用 a:hello 而是直接用 a.hello 调用成员函数。这个技巧并不非常有用,从效率角度上说,还是不用为好。

Comments

function class(super)
local class_type={}
class_type.ctor=false
class_type.super=super
class_type.new=function(...)
local obj={}
do
local create
create = function(c,...)
if c.super then
create(c.super,...)
end
if c.ctor then
c.ctor(obj,...)
end
end

create(class_type,...)
end
setmetatable(obj,{ __index=class_type })
return obj
end

if super then
setmetatable(class_type,{__index=
function(t,k)
local ret=super[k]
class_type[k]=ret
return ret
end
})
end
return class_type
end
这样写有什么问题?好像输出是一样的

直接setmetatable(obj,{__index=class_type})不就行了吗?

我实现了新的class:
1、支持super关键字
2、调用父类方法用super:xxx()
3、调用构造用super()

还有其他更OO的设计,请参考这里:http://next2d.com/?p=69

关于游戏的登陆编程过程,及其函数解释

setmetatable(obj,{ __index=_class[class_type] })

应该移动到local obj={}后面

不然ctor内无法调用函数

在子类的函数中调用super.xxx怎么实现?
目前我只想到在new函数中插一句obj.super = _class[super]

使用时self.super.xxx(self,....)

有没有办法去掉这里两个self

是我看的Lua参考手册太老了还是怎么回事,虽然里面也介绍了:语法糖果,可并没有说self是不是一个关键字或保留字啊,

有点小疑惑...

所有脚本语言都被人称为scheme的近亲。我还觉得C++的模板也简直就跟Lisp一个样呢

我认为用 lua 表现 OO ,比 C++ 强,不弱于 ruby 。

btw, lua 是个很优美的语言 IMHO 。lua 应该属于 scheme 的近亲。

使用lua就不应该OO。麻烦而且难于理解。 Lua 不是为OO设计的。

: 在 lua 里只是一个语法糖。

函数调用的时候 obj:func(arg) 其实等价于 obj.func(obj,arg)

函数定义时 function obj:func(arg) 等价于 function obj.func(self,arg)

在考虑问题的时候,可以无试 : 的存在,脑子里转换成以上 . 的形式看就可以了。

嗯,谢谢,我也怀疑查找这个东西的时候指向了闭包。
还有一个问题:
我如果在c里面call一个table里面的方法。
如果lua里面的函数有的写作:
function a.b(x, y)
end
有的写作
function a:c(x, y)
end
当写作a.b(x, y)时候我们不需要把self push进去。
当写作a:c(x, y)的时候我们应该把 self push进去。
问题是我在c里面不知道究竟写成了哪一种,问有没有办法在c里面知道究竟是写成了哪一种呢?谢谢!

这里有点问题,出在这里:

<pre>
_class[vtbl]=function (t,k)
return function(...)
return vtbl[k](t,...)
end
end
</pre>

因为你第一次访问 vtbl 的时候,就生成了一个 closure ,导致以后的对之的访问导向了这个 closure 。

我把 wiki 上的代码改了,直接把 vtbl 设置 到 metatable __index 中去。

不过这样,就必须用 : 而不是 . 来做成员函数调用。(当然这样也符合 lua 的习惯)

有个问题,不大明白是怎么回事:
a = class()
b = a.new()
print(b.c) --输出:function: 0046e970

b.c = nil
print(b.c) --输出:function: 0082340

为什么不是输出nil呢?想不明白!
请教各位前辈!

这是叫 override , 不过我喜欢把这个词译为重载

a.hello() -- 输出 hello test ,这个函数被重载了。

---------------------------
这应该不叫overload吧?应该是override!

我个人认为lua在语言层次实现class意义不大,反而失去了lua的部分灵活性。

其实 lua 也是一步步设计出来的,metatable 是在 lua 的第 5 版才加入。而且在 lua 5 诞生了很久,大部分习惯使用 lua 的人还是当成 lua 4 在用。

怎么用好 metatable 还有很多可挖掘的地方。

我觉得这里提供的方案比 pil 上的方案更完善的其实并不是让类的定义和继承更直观,而是提供了一个自然的 ctor 。这个 ctor 比 C++ 更方便就是可以向基类传递构造参数。

实现一个 dtor 也并不难,而且还可以利用一个 weaktable cache 住不要的对象,等再次构造同类对象的时候重用。

我参与的项目里面也实现了lua中的class机制,这里趁机小结一下:p
lua里面实现class机制,比较关键的地方就是class的继承机制,以及class实例化的过程。

class继承机制的关键是怎么让子类拥有父类的方法集:<br>
1.完全使用setmetatable()的方式实现,每继承一次,都把父类的方法集设置为子类方法集的metatable。这样做应该最符合class的继承的思想,尽可能的复用逻辑,而且可以做到动态更新父类的方法集后,所有子类都会焙蛳碛姓庀罡碌奶匦?比如满足某些特殊的动态更新需求)。但随着class继承的层次加深,会生成一个复杂的class方法集table(n层的metatable),毕竟metatable是有额外开销的,所以这个方法不一定是最完美的方案。<br>
2.将父类方法集copy到子类方法集来实现继承,即每当定义一个新的子类时,都将父类中方法集完整copy到子类方法集中。这种方法避免了使用metatable()带来的额外开销,但却造成了一些数据冗余(其实并不多),并丧失了父类更新子类也会自动更新的特性。<br>
3.方案2的改进版本(也就是云风这里使用的比较强悍的方式:P pf),即同样是采用copy父类方法集的方案,但却改进了copy的机制。将原本在class定义期执行的方法集copy工作转移到实例的运行期间,采用copy-on-use(等同于copy-on-write的设计思路)的方式,在子类实例用到父类的某个方法时,才将其copy到子类的方法集中。由于这种copy只发生一次,而且不见得子类会用到父类方法集中的所有内容(事实如此),所以这个方案相对于方案2来说减少了冗余数据,但却几乎没有增加任何额外开销。

class实例化关键是实例如何享有class的方法集:<br>
1.最烂的方式,方法集copy,即class实例化时,将class的方法集直接copy给实例的数据table。这样的好处就是每个实例创建后,外界除非直接操作实例的数据table,否则其它行为都不会影响到这个实例的所有属性和特征(也许可以满足某些特殊的需求吧),同时也省掉了一次metatable的查找开销。缺点很明显,实例化过程低效,而且产生大量的冗余信息(或者这里也采用copy-on-use的思想? :p)。<br>
2.采用将class方法集设置为实例的metatable的方式,使实例享有class的方法集(这要求实例的数据类型必须可以拥有自己的metatable,即只能是table或userdata)。这个方案更优雅一些,而且符合class的共享思想并且实例化开销很小,应该是实现实例化的最佳方案了。在实现子类的初始化函数时,一般的思路都是先生成一个父类的实例,再强制将当前子类的方法集设置为这个实例的metatable。

目前我们采用的class机制就是上述的两个2号方案(考虑更新到云风的3号方案),自己实现了两个接口class_define()、class_inherit(),其实完成的功能等同于以上代码中的class()。还是很pf lua的作者,估计在设计lua时就考虑到了面向对象的实现问题,很class的多关键机制(如隐式self参数、metatable机制)都已实现,这为自己实现class机制提供了强有力的支持。加上脚本的弱数据类型特性,只要思路清晰,在C++中可以实现的class机制lua中基本都能实现一个翻版,也许下个版本的lua可能会显示支持class:)

Post a comment

非这个主题相关的留言请到:留言本