第一版是為 Lua 5.0 編寫的。雖然在很大程度上仍然與後續版本相關,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您也可以協助 支援 Lua 專案


16.1 – 類別

類別用於建立物件的模型。許多 OO 語言提供類別的概念。在這些語言中,每個物件都是特定類別的執行個體。Lua 沒有類別的概念;每個物件都定義自己的行為,並有自己的形狀。不過,遵循 Self 和 NewtonScript 等基於原型的語言,在 Lua 中模擬類別並不困難。在這些語言中,物件沒有類別。相反地,每個物件可能有一個原型,它是一個常規物件,第一個物件會在其中尋找它不知道的任何運算。若要表示這些語言中的類別,我們只需建立一個物件,專門用作其他物件(其執行個體)的原型。類別和原型都用於放置多個物件要共用的行為。

在 Lua 中,使用我們在前一章節中看到的繼承概念,可以輕易地實作原型。更具體地說,如果我們有兩個物件 ab,我們只需執行下列動作,就能讓 b 成為 a 的原型:

    setmetatable(a, {__index = b})
之後,a 會在 b 中尋找它沒有的任何運算。將 b 視為物件 a 的類別,這只不過是術語上的改變。

讓我們回到銀行帳戶的範例。若要建立行為類似於 Account 的其他帳戶,我們安排這些新物件繼承 Account 的運作,使用 __index 元方法。請注意一個小最佳化,我們不需要建立一個額外的表格作為帳戶物件的元表格;我們可以使用 Account 表格本身來達到此目的

    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 會將 Account(在呼叫 Account:new 中的 self)作為其元表格。然後,當我們呼叫 a:deposit(100.00) 時,我們實際上呼叫 a.deposit(a, 100.00)(冒號僅為語法糖)。然而,Lua 無法在表格 a 中找到 "deposit" 項目;因此,它會查看元表格的 __index 項目。現在的情況大致如下
    getmetatable(a).__index.deposit(a, 100.00)
a 的元表格是 Account,而 Account.__index 也是 Account(因為新方法執行 self.__index = self)。因此,我們可以將前一個表達式改寫為
    Account.deposit(a, 100.00)
也就是說,Lua 呼叫原始的 deposit 函式,但將 a 傳遞為 self 參數。因此,新帳戶 aAccount 繼承了 deposit 函式。透過相同的機制,它可以繼承 Account 中的所有欄位。

繼承不僅適用於方法,也適用於新帳戶中不存在的其他欄位。因此,類別不僅提供方法,也提供其執行個體欄位的預設值。請記住,在我們對 Account 的第一個定義中,我們提供了值為 0 的欄位 balance。因此,如果我們建立一個沒有初始餘額的新帳戶,它將繼承這個預設值

    b = Account:new()
    print(b.balance)    --> 0
當我們在 b 上呼叫 deposit 方法時,它會執行等同於
    b.balance = b.balance + v
(因為 selfb)。表達式 b.balance 會評估為零,而初始存款會指定給 b.balance。下次我們詢問這個值時,不會呼叫索引元方法(因為現在 b 有自己的 balance 欄位)。