第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但存在一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还将帮助支持 Lua 项目


16.3 – 多重继承

由于对象在 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,它只有两个方法,setnamegetname

    Named = {}
    function Named:getname ()
      return self.name
    end
    
    function Named:setname (n)
      self.name = n
    end
要创建一个新类 NamedAccount,它是 AccountNamed 的子类,我们只需调用 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})
      ...
通过此技巧,对继承方法的访问与对本地方法的访问一样快(首次访问除外)。缺点是,在系统运行后很难更改方法定义,因为这些更改不会沿层次结构链向下传播。