16.3 多重继承

由于Lua中的对象不是元生(primitive)的,所以在Lua中有很多方法可以实现面向对象的程序设计。我们前面所见到的使用index metamethod的方法可能是简洁、性能、灵活各方面综合最好的。然而,针对一些特殊情况也有更适合的实现方式。下面我们在Lua中多重继承的实现。

实现的关键在于:将函数用作__index。记住,当一个表的metatable存在一个__index函数时,如果Lua调用一个原始表中不存在的函数,Lua将调用这个__index指定的函数。这样可以用__index实现在多个父类中查找子类不存在的域。

多重继承意味着一个类拥有多个父类,所以,我们不能用创建一个类的方法去创建子类。取而代之的是,我们定义一个特殊的函数createClass来完成这个功能,将被创建的新类的父类作为这个函数的参数。这个函数创建一个表来表示新类,并且将它的metatable设定为一个可以实现多继承的__index metamethod。尽管是多重继承,每一个实例依然属于一个在其中能找得到它需要的方法的单独的类。所以,这种类和父类之间的关系与传统的类与实例的关系是有区别的。特别是,一个类不能同时是其实例的metatable又是自己的metatable。在下面的实现中,我们将一个类作为他的实例的metatable,创建另一个表作为类的metatable

-- look up for `k' in list of tables 'plist'

local function search (k, plist)

    for i=1, table.getn(plist) do

       local v = plist[i][k]    -- try 'i'-th superclass

       if v then return v end

    end

end

 

function createClass (...)

    local c = {}      -- new class

 

    -- class will search for each method in the list of its

    -- parents (`arg' is the list of parents)

    setmetatable(c, {__index = function (t, k)

 

    return search(k, arg)

end})

 

-- prepare `c' to be the metatable of its instances

c.__index = c

 

-- define a new constructor for this new class

function c:new (o)

    o = o or {}

    setmetatable(o, c)

    return o

end

 

-- return new class

    return c

end

让我们用一个小例子阐明一下createClass的使用,假定我们前面的类Account和另一个类NamedNamed只有两个方法setname and getname

Named = {}

function Named:getname ()

    return self.name

end

 

function Named:setname (n)

    self.name = n

end

为了创建一个继承于这两个类的新类,我们调用createClass

NamedAccount = createClass(Account, Named)

为了创建和使用实例,我们像通常一样:

account = NamedAccount:new{name = "Paul"}

print(account:getname())    --> Paul

现在我们看看上面最后一句发生了什么,Luaaccount中找不到getname,因此他查找accountmetatable__index,即NamedAccount。但是,NamedAccount也没有getname,因此Lua查找NamedAccount metatable__index,因为这个域包含一个函数,Lua调用这个函数并首先到Account中查找getname,没有找到,然后到Named中查找,找到并返回最终的结果。当然,由于搜索的复杂性,多重继承的效率比起单继承要低。一个简单的改善性能的方法是将继承方法拷贝到子类。使用这种技术,index方法如下:

...

 

setmetatable(c, {__index = function (t, k)

    local v = search(k, arg)

    t[k] = v      -- save for next access

    return v

end})

 

...

应用这个技巧,访问继承的方法和访问局部方法一样快(特别是第一次访问)。缺点是系统运行之后,很难改变方法的定义,因为这种改变不能影响继承链的下端。


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛