一个类就是一个创建对象的模具。对于一些基于原型的语言,对象是没有“类型”的,而是每个对象都有一个原型(prototype).
原型也是一种常规的对象。当其他对象(类的实例)遇到一个未知操作时,原型会先查找它。这种语言要创建一个类,只需要创建一个专用作其他对象的原型。
类和原型都是一种组织对象间共享行为的方式。
在Lua中,可以用继承实现原型。如果有两个对象a和b,要让b作为a的原型:
setmetatable(a, {__index = b})
在此之后,a就会在b中查找所有它没有的操作。将b称为是对象a的类,只不过是术语上的一个变化。
回到上面的Account例子中,为了创建更多与Account行为类似的账号,可以让这些新对象从Account行为中继承这些操作。
具体做法是使用__index元方法。可以用一项小优化,则无须创建一个额外的table作为账户对象的元表。而是使用Account table自身作为元素:
function Account:new(o) o = o or {} --如果用户没有提供,就创建一个空表 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会将Account作为元表,当调用a:deposit(100.00)时,就是调用了a.deposit(a, 100.00).
当Lua无法在table a中找到条目deposit时,会进一步搜索元表的__index条目。
getmetatable(a).__index.deposit(a, 100.00)
a的元表是Account,Account.__index也是Account。因此,上面的表达式可以简化为:
Account.deposit(a , 100.00)
结果为Lua调用了原来的deposit函数,但传入a作为self参数。因此新账号a从Account继承了deposit函数。
继承不仅可以作用于方法,也可以作用于所有其他在新账户中没有的字段。
比如上面的balance field。如果在创建新账户时没有提供balance的初值,那么它就会继承这个默认值。
b = Account:new() print(b. balance) --> 0
在b上调用deposit时,self就是b,相当于:
b.balance = b.balance + v
在第一次调用deposit时,对b.balance的求值结果为0,然后一个初值被赋予了b.balance。
后续对b.balance的访问就不会再涉及到__index元方法了,因为此时b已有自己的balance字段。
以上内容来自:《Lua程序设计第二版》和《Programming in Lua third edition 》