16.1 类

一些面向对象的语言中提供了类的概念,作为创建对象的模板。在这些语言里,对象是类的实例。Lua不存在类的概念,每个对象定义他自己的行为并拥有自己的形状(shape)。然而,依据基于原型(prototype)的语言比如SelfNewtonScript,在Lua中仿效类的概念并不难。在这些语言中,对象没有类。相反,每个对象都有一个prototype(原型),当调用不属于对象的某些操作时,会最先会到prototype中查找这些操作。在这类语言中实现类(class)的机制,我们创建一个对象,作为其它对象的原型即可(原型对象为类,其它对象为类的instance)。类与prototype的工作机制相同,都是定义了特定对象的行为。

Lua中,使用前面章节我们介绍过的继承的思想,很容易实现prototypes.更明确的来说,如果我们有两个对象ab,我们想让b作为aprototype只需要:

setmetatable(a, {__index = b})

这样,对象a调用任何不存在的成员都会到对象b中查找。术语上,可以将b看作类,a看作对象。回到前面银行账号的例子上。为了使得新创建的对象拥有和Account相似的行为,我们使用__index metamethod,使新的对象继承Account。注意一个小的优化:我们不需要创建一个额外的表作为account对象的metatable;我们可以用Account表本身作为metatable

function Account:new (o)

    o = o or {}   -- create object if user does not provide one

    setmetatable(o, self)

    self.__index = self

    return o

end

(当我们调用Account:new时,self等于Account;因此我们可以直接使用Account取代self。然而,使用self在我们下一节介绍类继承时更合适)。有了这段代码之后,当我们创建一个新的账号并且掉用一个方法的时候,有什么发生呢?

a = Account:new{balance = 0}

a:deposit(100.00)

当我们创建这个新的账号a的时候,aAccount作为他的metatable(调用Account:new时,selfAccount)。当我们调用a:deposit(100.00),我们实际上调用的是a.deposit(a,100.00)(冒号仅仅是语法上的便利)。然而,Lua在表a中找不到deposit,因此他回到metatable__index对应的表中查找,情况大致如下:

getmetatable(a).__index.deposit(a, 100.00)

ametatableAccountAccount.__index也是Account(因为new函数中self.__index = self)。所以我们可以重写上面的代码为:

Account.deposit(a, 100.00)

也就是说,Lua传递a作为self参数调用原始的deposit函数。所以,新的账号对象从Account继承了deposit方法。使用同样的机制,可以从Account继承所有的域。继承机制不仅对方法有效,对表中所有的域都有效。所以,一个类不仅提供方法,也提供了他的实例的成员的默认值。记住:在我们第一个Account定义中,我们提供了成员balance默认值为0,所以,如果我们创建一个新的账号而没有提供balance的初始值,他将继承默认值:

b = Account:new()

print(b.balance)     --> 0

当我们调用bdeposit方法时,实际等价于:

b.balance = b.balance + v

(因为self就是b)。表达式b.balance等于0并且初始的存款(b.balance)被赋予b.balance。下一次我们访问这个值的时候,不会在涉及到index metamethod,因为b已经存在他自己的balance域。


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