Lua 無內置類,**通過表 + 元表 模擬原型繼承**實現 OOP。本文從核心機制 (元表、__index、self) 入手,逐步構建:類工廠、實例化、繼承鏈、多態調用、多重繼承、私有成員模式,給出生產級可復用代碼框架。
a = {}; b = a 共享同一張表;a == b 比較引用地址。obj:method(args) ⇔ obj.method(obj, args),隱式傳遞 self。__index:查找鍵失敗時,若元表有 __index 表/函數,則轉向該表/函數繼續查找 → **繼承鏈核心**。-- class.lua: 通用類構建器
local function class(base)
local c = {} -- 新類表 (原型)
c.__index = c -- 關鍵:自身作為 __index
setmetatable(c, base) -- 繼承基類 (若 base 為 nil 則無父類)
-- 構造函數
function c:new(o)
o = o or {}
setmetatable(o, c) -- 實例元表指向類
return o
end
return c
end
return class
local class = require "class"
-- 基類 Account
local Account = class()
Account.balance = 0 -- 類屬性 (默認值)
function Account:deposit(v) -- 方法 (冒號語法隱式 self)
self.balance = self.balance + v
end
function Account:withdraw(v)
if self.balance >= v then
self.balance = self.balance - v
return true
end
return false
end
function Account:print()
print("Balance:", self.balance)
end
-- 派生類 SavingsAccount (繼承)
local SavingsAccount = class(Account)
SavingsAccount.interest_rate = 0.03
function SavingsAccount:add_interest()
self:deposit(self.balance * self.interest_rate) -- 多態調用基類方法
end
-- 重寫方法
function SavingsAccount:print()
print("Savings Balance:", self.balance, "Rate:", self.interest_rate)
end
-- 實例化與測試
local acc = Account:new{balance = 100}
acc:deposit(50)
acc:print() -- Balance: 150
local sav = SavingsAccount:new{balance = 1000}
sav:add_interest()
sav:print() -- Savings Balance: 1030 Rate: 0.03
sav:withdraw(200)
sav:print() -- Savings Balance: 830 Rate: 0.03
-- 類創建時
setmetatable(Child, Parent) -- Child 的元表 = Parent
Child.__index = Child -- Child 自己的 __index = Child
-- 實例創建時
setmetatable(instance, Child) -- instance 元表 = Child
-- 查找鍵 k 流程
instance.k
→ instance 中找不到
→ 元表 Child 中找 k (因 Child.__index = Child)
→ Child 中找不到
→ Child 元表 Parent 中找 k (因 Parent.__index = Parent)
→ ... 直到頂層基類或 nil
**關鍵點**:類表同時扮演「原型」(存方法/默認屬性) 和「元表」(提供 __index) 角色;實例只存**實例特有數據**,方法/默認值全在類鏈上。
Lua 單繼承鏈,但可通過 **Mixin 表** 組合行為:
local class = require "class"
-- Mixin: 可序列化
local Serializable = {}
function Serializable:serialize()
local t = {}
for k, v in pairs(self) do
if type(v) ~= "function" then t[k] = v end
end
return t
end
-- Mixin: 可比較
local Comparable = {}
function Comparable:eq(other)
return self.id == other.id
end
-- 組合類
local User = class()
function User:new(o)
o = o or {}
-- 手動混入 Mixin 方法 (淺拷貝)
for k, v in pairs(Serializable) do User[k] = v end
for k, v in pairs(Comparable) do User[k] = v end
setmetatable(o, User)
return o
end
local u = User:new{id=1, name="Alice", pwd="secret"}
print(u:serialize()) -- {id=1, name="Alice"} (pwd 非函數但屬敏感,實際需過濾)
print(u:eq({id=1})) -- true
**生產級多重繼承**推薦用 middleclass、30log 等成熟庫,自動處理鑽石繼承、方法解析順序 (MRO)。
function newCounter()
local count = 0 -- 真私有,外部不可訪問
return {
inc = function() count = count + 1; return count end,
get = function() return count end
}
end
local c = newCounter()
print(c.inc()) -- 1
print(c.get()) -- 1
-- c.count 不存在
local Private = setmetatable({}, {__mode = "k"}) -- 弱鍵表,不阻礙 GC
local MyClass = class()
function MyClass:new(o)
o = o or {}
Private[o] = {secret = 42} -- 私有數據綁定實例
setmetatable(o, MyClass)
return o
end
function MyClass:reveal()
return Private[self].secret
end
| 場景 | 推薦模式 |
|---|---|
| 簡單單繼承、輕量項目 | 自製 class() 工廠 (本文核心代碼) |
| 複雜繼承、MRO、大項目 | middleclass / 30log / classic 成熟庫 |
| 高性能、大量實例 | 元表復用 + __index 鏈,**避免閉包工廠** (每實例複製方法) |
| 真私有、安全敏感 | 閉包工廠 或 弱表私有數據 |
| 組合優於繼承 | Mixin 表 + 手動混入 或 組合對象持有 |
instance.attr = val 會在實例創建新鍵,**不影響類默認值**;但若為表類型且未深拷貝,修改內容會影響所有實例。nil 或無 __index 的表。__mode="k/v") 防止私有數據表阻礙實例 GC。