第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但存在一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还将帮助支持 Lua 项目。
用 Lua 编程 | ||
第二部分。表和对象 第 16 章。面向对象编程 |
由于对象在 Lua 中不是基本类型,因此有几种方法可以在 Lua 中进行面向对象编程。我们之前看到的,使用索引元方法的方法可能是简单性、性能和灵活性方面的最佳组合。然而,还有其他实现,可能更适合某些特定情况。这里我们将看到一个允许在 Lua 中进行多重继承的备选实现。
此实现的关键是将函数用于元字段 __index
。请记住,当表的元表在字段 __index
中有函数时,Lua 将在无法在原始表中找到键时调用该函数。然后,__index
可以按需要在多少个父级中查找缺失的键。
多重继承意味着一个类可能有多个超类。因此,我们不能使用类方法来创建子类。相反,我们将为此目的定义一个特定函数 createClass
,其参数为新类的超类。此函数创建一个表来表示新类,并使用执行多重继承的 __index
元方法设置其元表。尽管有多重继承,但每个实例仍然属于一个单独的类,它在其所有方法中查找。因此,类和超类之间的关系不同于类和实例之间的关系。特别是,一个类不能同时作为其实例的元表和其自己的元表。在以下实现中,我们将一个类作为其实例的元表,并创建另一个表作为类的元表。
-- 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
,它只有两个方法,setname
和 getname
Named = {} function Named:getname () return self.name end function Named:setname (n) self.name = n end要创建一个新类
NamedAccount
,它是 Account
和 Named
的子类,我们只需调用 createClass
NamedAccount = createClass(Account, Named)要创建和使用实例,我们照常进行
account = NamedAccount:new{name = "Paul"} print(account:getname()) --> Paul现在让我们了解最后一条语句中发生的情况。Lua 无法在
account
中找到 "getname"
字段。因此,它在 account
的元表(即 NamedAccount
)中查找 __index
字段。但是 NamedAccount
也无法提供 "getname"
字段,因此 Lua 在 NamedAccount
的元表的 __index
字段中查找。由于此字段包含一个函数,因此 Lua 会调用它。然后,此函数首先在 Account
中查找 "getname"
,但未成功,然后在 Named
中查找,并在其中找到一个非 nil 值,这是搜索的最终结果。
当然,由于此搜索的底层复杂性,多重继承的性能与单一继承不同。改善此性能的一种简单方法是将继承的方法复制到子类中。使用此技术,类的索引元方法将如下所示
... setmetatable(c, {__index = function (t, k) local v = search(k, arg) t[k] = v -- save for next access return v end}) ...通过此技巧,对继承方法的访问与对本地方法的访问一样快(首次访问除外)。缺点是,在系统运行后很难更改方法定义,因为这些更改不会沿层次结构链向下传播。
版权所有 © 2003–2004 Roberto Ierusalimschy。保留所有权利。 |