分享

Lua 面向对象实现

 爱蓝斯 2013-12-31

目标:实现一个class函数,给lua添加面向对象的特性

基础:和编译型的面向对象语言不同,在lua中不存在类的定义这样一个概念,不管是类的定义还是类的实例都需要通过lua table来模拟。我们实现的lua面向对象是prototype方式的,即类是一个lua table,这个table 定义了类实例的原型, 类的实例则是基于这个原型的另一个lua table。

关键:实现Lua面向对象可以分解为类的定义和类的实例化两个问题。类的定义主要是实现继承,即怎么让子类拥有父类的方法集。类的实例化需要解决实例如何共享类的方法集,但独享自己的成员变量实例。

方案:子类在定义时复制所有基类的方法,在实例化时将该类作为metatable的__index赋值给实例。这就是cocos2dx里面的lua class的实现。

  1. function class(classname, super)  
  2.      local cls = {}  
  3.      if super then --复制基类方法  
  4.         cls = {}  
  5.         for k,v in pairs(super) do cls[k] = v end  
  6.         cls.super = super  
  7.     else  
  8.         cls = {ctor = function() end}  
  9.     end  
  10.   
  11.     cls.__cname = classname  
  12.     cls.__index = cls  
  13.   
  14.     function cls.new(...) --实例化  
  15.         local instance = setmetatable({}, cls)  
  16.         instance.class = cls  
  17.         instance:ctor(...)  
  18.         return instance  
  19.     end  
  20.     return cls  
  21. end  

在这个实现方式中,所有子类的实例都共享了类原型中的方法。设想我们在类ClassA中定义了方法A,当调用实例的方法A时,lua解释器会先在实例table里面找方法A,因为我们没有在实例中添加方法,自然是找不到,于是解释器会通过实例的metatable里面的__index字段来找方法A,这个__index字段其实就是类原型,于是方法A被找到调用成功了。现在我们调用实例的方法B, B不是在ClassA中定义的,而是在ClassA的基类ClassB中定义的,由于ClassA在定义的时候已经把基类ClassB的方法全部复制了一遍,所以解释器仍然可以成功调用到B,继承实现了。

但是这个实现有个严重的问题,类的成员变量没有继承下来。看下面一个测试:

  1. BaseClass = class("BaseClass", nil)  
  2.   
  3. function BaseClass:ctor(param)  
  4.      print("baseclass ctor")  
  5.      self._param = param  
  6.      self._children = {}  
  7. end  
  8.   
  9. function BaseClass:addChild(obj)  
  10.      table.insert(self._children, obj)  
  11. end  
  12.   
  13. DerivedClass = class("DerivedClass", BaseClass)  
  14.   
  15. function DerivedClass:ctor(param)  
  16.      print("derivedclass ctor")  
  17. end  
  18.   
  19. local instance = DerivedClass.new("param1")  
  20. instance:addChild("child1")  

运行这个测试,我们会得到两行输出:

  1. derivedclass ctor  
  2. bad argument #1 to 'insert' (table expected, got nil)  

self._children为什么是nil呢?从输出我们看出子类的实例确实成功调用了父类的addChild方法,但是这个方法调用失败了,因为self._children是nil。再细看我们发现基类的构造函数根本没有调用,我们的self._children = {}是放在基类的构造函数里面的,没有调用基类的构造函数自然self._children是nil了。

好,看来这个class实现不完美,我们做一个修补,在子类的构造里面调用一下父类的构造函数。

  1. function DerivedClass:ctor(param)  
  2.      self.super:ctor(param)  
  3.      print("derivedclass ctor")  
  4. end  

再次运行测试,我们得到了三行输出:
  1. baseclass   
  2. ctorderivedclass  
  3. ctorclasstest1.lua:32: bad argument #1 to 'insert' (table expected, got nil)  

这回基类的构造函数成功调用了,可为什么self._children依然是nil?

把instance的内容dump出来我们发现调用了基类的构造函数后_children被添加到基类的原型中去了,并没有添加到我们子类的实例中,这样在子类实例的self中去找_children自然是找不到了。

  1. <1>{  
  2.   class = <2>{  
  3.     __cname = "DerivedClass",  
  4.     __index = <table 2>,  
  5.     addChild = <function 1>,  
  6.     ctor = <function 2>,  
  7.     new = <function 3>,  
  8.     super = <3>{  
  9.       __cname = "BaseClass",  
  10.       __index = <table 3>,  
  11.       _children = <4>{},  
  12.       _param = <table 1>,  
  13.       addChild = <function 1>  
  14.       ctor = <function 4>,  
  15.       new = <function 5>  
  16.     }  
  17.   },  
  18.   <metatable> = <table 2>  
  19. }  

基类的原型中根本不应该有成员变量,所以我们这个修补并不是我们想要的。当然我们这样改就没有问题了:
  1. function DerivedClass:ctor(param)  
  2.      self._children = {}  
  3.      print("derivedclass ctor")  
  4. end  

可是如果在基类中定义好的成员在子类中还要定义一遍,这可不是我们想要的面向对象啊。看来cocos2dx的lua class 实现是有问题的。

修正:上面方法的类定义部分其实没有问题,但是实例化部分粗糙了。调用基类的构造函数时所使用的self指的是基类的原型table,而我们希望的是指向实例table,原型只需要提供方法,不需实例化成员变量,于是我们想到了做如下修改。

  1. function class(classname, super)  
  2.      local cls = {}  
  3.      if super then  
  4.         cls = {}  
  5.         for k,v in pairs(super) do cls[k] = v end  
  6.         cls.super = super  
  7.     else  
  8.         cls = {ctor = function() end}  
  9.     end  
  10.   
  11.     cls.__cname = classname  
  12.     cls.__index = cls  
  13.   
  14.     function cls.new(...)  
  15.         local instance = setmetatable({}, cls)  
  16.         local create  
  17.         create = function(c, ...)  
  18.              if c.super then -- 递归向上调用create  
  19.                   create(c.super, ...)  
  20.              end  
  21.              if c.ctor then  
  22.                   c.ctor(instance, ...)  
  23.              end  
  24.         end  
  25.         create(instance, ...)  
  26.         instance.class = cls  
  27.         return instance  
  28.     end  
  29.     return cls  
  30. end  

再运行测试,我们得到了正确的结果。打印出子类实例的结构,我们看到_children现在是实例table的成员变量了:

  1. <1>{  
  2.   _children = <2>{ "child1" },  
  3.   _param = "param1",  
  4.   class = <3>{  
  5.     __cname = "DerivedClass",  
  6.     __index = <table 3>,  
  7.     addChild = <function 1>,  
  8.     ctor = <function 2>,  
  9.     new = <function 3>,  
  10.     super = <4>{  
  11.       __cname = "BaseClass",  
  12.       __index = <table 4>,  
  13.       addChild = <function 1>,  
  14.       ctor = <function 4>,  
  15.       new = <function 5>  
  16.     }  
  17.   },  
  18.   <metatable> = <table 3>  
  19. }  


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多