lua中面向对象(class)实现探索(二)

来源:互联网 发布:mac和nars口红哪个滋润 编辑:程序博客网 时间:2024/05/17 06:33

说明:本文亦作为某章节出现在中山大学某实验室编撰的某教材中,本博客博主即该教程的编撰者,因此请不要因为看到本博客和该书中某章内容相同而认为这之间必有作假必有一方抄袭另一方。

云风的实现十分精妙但功能却有限,原因在于这样的实现无法做到一个功能,即在子类的函数中调用父类的同名函数(当然父类的同名函数中可能又调用了父类的父类的同名函数),你可能会说类不是保存了一个super指针吗?我不可以这样吗?


这样是行不通的,原因在于super变量保存在类中,而self只能访问到类的vtbl中的属性或方法。

你又或许会说,那我这样呢?


这样也是不行的,此时super访问到了,但是hello()函数访问不到,原因也很简单请自行分析。

         为了解决上述的问题,我们对这份实现做出几个修改。

         首先为了解决能在对象中通过self来访问super变量这个问题,我们将对class_type设置元表的操作提到class_type.super被定义赋值之前,也即这样做:


这样做的原因很简单,将对class_type设置元表的操作提前,那么之后在class_type中进行的定义super属性(也包括ctor和new属性)就会因为元表中的元方法而最后被在vtbl中创建出,从而实现了在对象中通过self来访问。

         这样做了之后,通过self可以访问super 变量了,是否就意味着可以在子类函数中调用父类的同名函数了呢?比如这样:


看起来似乎没问题,实际上还是行不通的,问题在于” : ”(冒号)这个语法糖。 : 的作用在于它几乎以代码替换的方式将 : 左边紧接着的变量作为第一个参数传入了函数,因此self.super:hello()这句代码被lua虚拟机解释后的结果类似于下面这句:

self.super.hello(super )

看出问题来了吗?hello()函数这里原本希望接受的第一个参数应该是上述代码里的self(即实例化后的对象),也即上述代码原本期望被lua虚拟机解释的结果应该类似下面这句:

         self.super.hello(self )

         相对正确的做法是不利用语法糖,手动填写函数的第一个参数,也即直接写成期望被lua虚拟机解释后的样子:

         self.super.hello(self )

         至此,问题似乎得到了完美的解决,但真的如此吗?我们来考虑如下的情况,假设我们有这样的一份代码:

[plain] view plain copy
print?
  1. --父类  
  2. base = class()  
  3. function base:ctor(val)  
  4.     print("base ctor")  
  5.     self._cnt = val or 0  
  6. end  
  7. function base:show()  
  8.     print("in baseshow")  
  9. end  
  10.   
  11. --子类1  
  12. child1 = class(base)  
  13. function child1:ctor()  
  14.     print("child1 ctor")  
  15. end  
  16. function child1:show()  
  17.     self.super.show(self)  
  18.     print("in child1show ")  
  19. end  
  20.   
  21. --子类2  
  22. child2 = class(child1)  
  23. function child2:ctor()  
  24.     print("child2 ctor")  
  25. end  
  26. function child2:show()  
  27.     self.super.show(self)  
  28.     print("in child2show")  
  29. end  
  30.   
  31. function child2:create(...)  
  32.     local o = self.new(...)  
  33.     return o  
  34. end  
  35.   
  36. o = child2:create()  
  37. o:show()  

我们满怀期望的运行,但结果是:


很悲伤吧,lua vm一点面子都不给。它不给面子的原因在于当前的class实现在多层继承中多层调用super方法时产生溢出,也即上述测试代码中的child1:show()的第一行调用super方法本意是想调用base类中的同名方法,可是此时的self.super仍然为child1。因此在child1:show()反复调用自身,陷入不可饶恕的自递归深渊最终栈溢出。

三、Lua中类支持调用super方法的实现

         接着上文说,为了解决上面导致栈溢出的问题我们需要继续修改代码,在原有class函数中加入这么一段(这里只贴出代码段,最后会贴出最终的完整代码):

[plain] view plain copy
print?
  1. class_type.super = function(self, f, ...)  
  2.         assert(self and self.__type == 'object', string.format("'self' must be a object when call super(self, '%s', ...)", tostring(f)))  
  3.   
  4.         local originBase = self.__base  
  5.         --find the first f function that differ from self[f] in the inheritance chain  
  6.         local s     = originBase  
  7.         local base  = s.__base  
  8.         while base and s[f] == base[f] do  
  9.             s = base  
  10.             base = base.__base  
  11.         end  
  12.           
  13.         assert(base and base[f], string.format("base class or function cannot be found when call .super(self, '%s', ...)", tostring(f)))  
  14.         --now base[f] is differ from self[f], but f in base also maybe inherited from base's baseClass  
  15.         while base.__base and base[f] == base.__base[f] do  
  16.             base = base.__base  
  17.         end  
  18.   
  19.         -- If the base also has a baseclass, temporarily set :super to call that baseClass' methods  
  20.         -- this is to avoid stack overflow  
  21.         if base.__base then  
  22.             self.__base = base  
  23.         end  
  24.   
  25.         --now, call the super function  
  26.         local result = base[f](self, ...)  
  27.   
  28.         --set back  
  29.         if base.__base then  
  30.             self.__base = originBase  
  31.         end  
  32.   
  33.         return result  
  34.     end  

上述代码主要做了两个事:

1.      子类对象中调用super的f方法时,将沿着继承链表(super chain)自上而下在众父类中寻找第一个与自身f方法不同地址的类,那么这个类中的f方法就是需要寻找的。也即下面这段:

[plain] view plain copy
print?
  1. while base.__base and base[f] == base.__base[f] do  
  2.             base = base.__base  
  3.         end  

1.      找到同名不同地址的f方法后,判断当前类是否还有基类。如果有,则临时性的改变继承关系使得如果在该f方法中亦调用了super中的f方法,不至于栈溢出。

加入这个super函数后,子类对象中调用父类同名函数的方式改为:

                  self:super(‘funcName’, …)

         也即之前的测试代码中的如下两个函数:

[plain] view plain copy
print?
  1. function child1:show()  
  2.     self.super.show(self)  
  3.     print("in child1show ")  
  4. end  
  5.   
  6. function child2:show()  
  7.     self.super.show(self)  
  8.     print("in child2show ")  
  9. end  
分别改为:

[plain] view plain copy
print?
  1. function child1:show()  
  2.     self:super('show')  
  3.     print("in child1show ")  
  4. end  
  5.   
  6. function child2:show()  
  7.     self:super('show')  
  8.     print("in child2show")  
  9. end  
点击运行,看到:


没错,这正是我们想看到的。

最后贴一下完整的代码:

[html] view plain copy
print?
  1. -- Internal register  
  2. local _class={}  
  3.   
  4. function class(base)  
  5.     local class_type={}  
  6.   
  7.     class_type.__type   = 'class'  
  8.     class_type.ctor     = false  
  9.       
  10.     local vtbl = {}  
  11.     _class[class_type] = vtbl  
  12.     setmetatable(class_type,{__newindex = vtbl__index = vtbl})  
  13.   
  14.     if base then  
  15.         setmetatable(vtbl,{__index=  
  16.             function(t,k)  
  17.                 local ret=_class[base][k]  
  18.                 vtbl[k]=ret  
  19.                 return ret  
  20.             end  
  21.         })  
  22.     end  
  23.       
  24.     class_type.__base   = base  
  25.     class_type.new      = function(...)  
  26.         --create a object, dependent on .__createFunc  
  27.         local obj= {}  
  28.         obj.__base  = class_type  
  29.         obj.__type  = 'object'  
  30.         do  
  31.             local create  
  32.             create = function(c, ...)  
  33.                 if c.__base then  
  34.                     create(c.__base, ...)  
  35.                 end  
  36.                 if c.ctor then  
  37.                     c.ctor(obj, ...)  
  38.                 end  
  39.             end  
  40.   
  41.             create(class_type,...)  
  42.         end  
  43.   
  44.         setmetatable(obj,{ __index = _class[class_type] })  
  45.         return obj  
  46.     end  
  47.   
  48.     class_type.super = function(self, f, ...)  
  49.         assert(self and self.__type == 'object', string.format("'self' must be a object when call super(self, '%s', ...)", tostring(f)))  
  50.   
  51.         local originBase = self.__base  
  52.         --find the first f function that differ from self[f] in the inheritance chain  
  53.         local s     = originBase  
  54.         local base  = s.__base  
  55.         while base and s[f] == base[f] do  
  56.             s = base  
  57.             base = base.__base  
  58.         end  
  59.           
  60.         assert(base and base[f], string.format("base class or function cannot be found when call .super(self, '%s', ...)", tostring(f)))  
  61.         --now base[f] is differ from self[f], but f in base also maybe inherited from base's baseClass  
  62.         while base.__base and base[f] == base.__base[f] do  
  63.             base = base.__base  
  64.         end  
  65.   
  66.         -- If the base also has a baseclass, temporarily set :super to call that baseClass' methods  
  67.         -- this is to avoid stack overflow  
  68.         if base.__base then  
  69.             self.__base = base  
  70.         end  
  71.   
  72.         --now, call the super function  
  73.         local result = base[f](self, ...)  
  74.   
  75.         --set back  
  76.         if base.__base then  
  77.             self.__base = originBase  
  78.         end  
  79.   
  80.         return result  
  81.     end  
  82.   
  83.     return class_type  
  84. end  
http://blog.csdn.net/mywcyfl/article/details/37706247
原创粉丝点击