第一版是為 Lua 5.0 編寫的。雖然對後續版本仍有很大的關聯性,但有些地方有所不同。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買這本書,您也協助 支持 Lua 專案


16.3 – 多重繼承

由於物件在 Lua 中並非原始型別,因此有許多方法可以在 Lua 中執行物件導向程式設計。我們之前看到的 method,使用索引 metamethod,可能是簡單性、效能和彈性的最佳組合。不過,還有其他實作可能更適合某些特定情況。在這裡,我們將看到一個允許在 Lua 中進行多重繼承的替代實作。

此實作的關鍵是使用函式作為 metafield __index。請記住,當表格的 metatable 在 __index 欄位中有一個函式時,Lua 會在找不到原始表格中的金鑰時呼叫該函式。然後,__index 可以查詢它想要的任意數量的父類別中的遺失金鑰。

多重繼承表示一個類別可能有多個超類別。因此,我們無法使用類別 method 建立子類別。相反地,我們將定義一個特定函式來執行此目的,createClass,其參數為新類別的超類別。此函式會建立一個表格來表示新類別,並設定其 metatable,其中包含執行多重繼承的 __index metamethod。儘管有多重繼承,每個實例仍屬於單一類別,它會在其中尋找其所有 method。因此,類別和超類別之間的關係與類別和實例之間的關係不同。特別是,類別不能同時作為其實例的 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,它只有兩個 method,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 的元表中尋找 __index 欄位,也就是 NamedAccount。但 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})
      ...
使用這個技巧,存取繼承的方法會和存取本機方法一樣快(除了第一次存取)。缺點是,在系統執行後很難變更方法定義,因為這些變更不會傳播到階層鏈中。