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和另一个类Named,Named只有两个方法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
现在我们看看上面最后一句发生了什么,Lua在account中找不到getname,因此他查找account的metatable的__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})
...
应用这个技巧,访问继承的方法和访问局部方法一样快(特别是第一次访问)。缺点是系统运行之后,很难改变方法的定义,因为这种改变不能影响继承链的下端。