Lua继承(面向对象的程序设计)

继承

通常面向对象语言中,继承使得类可以访问其他类的方法,这在 Lua 中也很容易现实:
假定我们有一个基类 Account:

Account = {balance = 0} 
function Account:new (o) 
 o = o or {} 
 setmetatable(o, self) 
 self.__index = self 
return o 
end 
function Account:deposit (v) 
 self.balance = self.balance + v 
end 
function Account:withdraw (v) 
if v > self.balance then error"insufficient funds" end
 self.balance = self.balance - v 
end 

我们打算从基类派生出一个子类 SpecialAccount,这个子类允许客户取款超过它的存款余额限制,我们从一个空类开始,从基类继承所有操作:
SpecialAccount = Account:new()

到现在为止,SpecialAccount 仅仅是 Account 的一个实例。现在奇妙的事情发生了:

s = SpecialAccount:new{limit=1000.00} 
SpecialAccount 从 Account 继承了 new 方法,当 new 执行的时候,self 参数指向SpecialAccount。所以,s 的 metatable 是 SpecialAccount__index 也是 SpecialAccount。
这样,s 继承了 SpecialAccount,后者继承了 Account。当我们执行:s:deposit(100.00) 

Lua 在 s 中找不到 deposit 域,他会到 SpecialAccount 中查找,在 SpecialAccount 中找不到,会到 Account 中查找。使得 SpecialAccount 特殊之处在于,它可以重定义从父类中继承来的方法:

function SpecialAccount:withdraw (v) 
if v - self.balance >= self:getLimit() then
 error"insufficient funds"
end 
 self.balance = self.balance - v 
end 
function SpecialAccount:getLimit () 
return self.limit or 0 
end 

现在,当我们调用方法 s:withdraw(200.00),Lua 不会到 Account 中查找,因为它第一次救在 SpecialAccount 中发现了新的 withdraw 方法,由于 s.limit 等于 1000.00(记住我们创建 s 的时候初始化了这个值)程序执行了取款操作,s 的 balance 变成了负值。

在 Lua 中面向对象有趣的一个方面是你不需要创建一个新类去指定一个新的行为。如果仅仅一个对象需要特殊的行为,你可以直接在对象中实现,例如,如果账号 s 表示一些特殊的客户:取款限制是他的存款的 10%,你只需要修改这个单独的账号:

function s:getLimit () 
return self.balance * 0.10 
end 

这样声明之后,调用 s:withdraw(200.00)将运行 SpecialAccount 的 withdraw 方法,但是当方法调用 self:getLimit 时,最后的定义被触发。

多重继承

由于 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 方法如下:

扫描二维码关注公众号,回复: 9292236 查看本文章
... 
setmetatable(c, {__index = function (t, k) 
local v = search(k, arg) 
 t[k] = v -- save for next access 
return v 
end}) 
... 

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

发布了290 篇原创文章 · 获赞 153 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104404456
今日推荐