lua添加面向对象

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

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

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

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

[plain] view plain copy

function class(classname, super)  
     local cls = {}  
     if super then --复制基类方法  
        cls = {}  
        for k,v in pairs(super) do cls[k] = v end  
        cls.super = super  
    else  
        cls = {ctor = function() end}  
    end  

    cls.__cname = classname  
    cls.__index = cls  

    function cls.new(...) --实例化  
        local instance = setmetatable({}, cls)  
        instance.class = cls  
        instance:ctor(...)  
        return instance  
    end  
    return cls  
end  

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

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

[plain] view plain copy

BaseClass = class("BaseClass", nil)  

function BaseClass:ctor(param)  
     print("baseclass ctor")  
     self._param = param  
     self._children = {}  
end  

function BaseClass:addChild(obj)  
     table.insert(self._children, obj)  
end  

DerivedClass = class("DerivedClass", BaseClass)  

function DerivedClass:ctor(param)  
     print("derivedclass ctor")  
end  

local instance = DerivedClass.new("param1")  
instance:addChild("child1")  

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

[plain] view plain copy

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

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

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

[plain] view plain copy

function DerivedClass:ctor(param)  
     self.super:ctor(param)  
     print("derivedclass ctor")  
end  

再次运行测试,我们得到了三行输出:

[plain] view plain copy

baseclass   
ctorderivedclass  
ctorclasstest1.lua:32: bad argument #1 to 'insert' (table expected, got nil)  

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

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

[plain] view plain copy

<1>{  
  class = <2>{  
    __cname = "DerivedClass",  
    __index = <table 2>,  
    addChild = <function 1>,  
    ctor = <function 2>,  
    new = <function 3>,  
    super = <3>{  
      __cname = "BaseClass",  
      __index = <table 3>,  
      _children = <4>{},  
      _param = <table 1>,  
      addChild = <function 1>  
      ctor = <function 4>,  
      new = <function 5>  
    }  
  },  
  <metatable> = <table 2>  
}  

基类的原型中根本不应该有成员变量,所以我们这个修补并不是我们想要的。当然我们这样改就没有问题了:

[plain] view plain copy

function DerivedClass:ctor(param)  
     self._children = {}  
     print("derivedclass ctor")  
end  

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

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

[plain] view plain copy

function class(classname, super)  
     local cls = {}  
     if super then  
        cls = {}  
        for k,v in pairs(super) do cls[k] = v end  
        cls.super = super  
    else  
        cls = {ctor = function() end}  
    end  

    cls.__cname = classname  
    cls.__index = cls  

    function cls.new(...)  
        local instance = setmetatable({}, cls)  
        local create  
        create = function(c, ...)  
             if c.super then -- 递归向上调用create  
                  create(c.super, ...)  
             end  
             if c.ctor then  
                  c.ctor(instance, ...)  
             end  
        end  
        create(instance, ...)  
        instance.class = cls  
        return instance  
    end  
    return cls  
end  

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

[plain] view plain copy

<1>{  
  _children = <2>{ "child1" },  
  _param = "param1",  
  class = <3>{  
    __cname = "DerivedClass",  
    __index = <table 3>,  
    addChild = <function 1>,  
    ctor = <function 2>,  
    new = <function 3>,  
    super = <4>{  
      __cname = "BaseClass",  
      __index = <table 4>,  
      addChild = <function 1>,  
      ctor = <function 4>,  
      new = <function 5>  
    }  
  },  
  <metatable> = <table 3>  
}  

cocos2dx-lua function.lua 定义了class方法,让lua实现继承像传统语言一样漂亮和方便

看定义

[plain] view plain copy
print?

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  

写个测试代码,注意出错的部分

[plain] view plain copy
print?

local Base = class('Base')  
Base.__index = Base  

function Base:ctor(id)  
    print('Base:ctor',id)  
    self.id = id  
end  

local ExtBase = class('ExtBase',Base)  
ExtBase.__index = ExtBase  

function ExtBase:ctor(id)  
    --Base:ctor(id)  
    super(self,id)  
    print('ExtBase:ctor',id)  
end  

受传统语言影响,会在子类调用基类的构造函数,而事实上,这导致直接将类型本身作为对象实例传入

导致self指向是那个类型本身(也是个table)

那就只能这么写了

[plain] view plain copy
print?

Base.ctor(self,id)  

有点丑了样,封装一下super函数,看起来好看一点。。

转载:http://blog.csdn.net/ring0hx/article/details/16341261
http://blog.csdn.net/myerel/article/details/38706225

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值