返回首页

教程导航

返回首页

🏷️ 标签(1)

🗂️ 分类(1)

第 12 章 面向对象 Lua

第 12 章 面向对象 Lua

Lua 没有内置类,但表、函数和元表足以实现对象系统。理解这些机制比记某个“类库语法”更重要,因为不同项目的 Lua 面向对象实现可能不同。

用表表示对象

最简单的对象是一个包含数据与函数的表:

local account = {
  owner = "Ada",
  balance = 100
}

function account.deposit(self, amount)
  self.balance = self.balance + amount
end

account.deposit(account, 50)
print(account.balance) -- 150

使用冒号语法时,self 会自动作为第一个参数传入:

function account:withdraw(amount)
  if amount > self.balance then
    return nil, "insufficient balance"
  end
  self.balance = self.balance - amount
  return self.balance
end

account:withdraw(25)

每个对象都复制方法会浪费内存。更常见的做法是把方法放在共享表里。

类与实例

创建类表,并让实例通过 __index 查找方法:

local Account = {}
Account.__index = Account

function Account.new(owner, balance)
  return setmetatable({
    owner = owner,
    balance = balance or 0
  }, Account)
end

function Account:deposit(amount)
  if type(amount) ~= "number" or amount <= 0 then
    return nil, "amount must be positive"
  end

  self.balance = self.balance + amount
  return self.balance
end

function Account:withdraw(amount)
  if amount <= 0 then
    return nil, "amount must be positive"
  end
  if amount > self.balance then
    return nil, "insufficient balance"
  end

  self.balance = self.balance - amount
  return self.balance
end

local account = Account.new("Ada", 100)
account:deposit(50)
print(account.balance) -- 150

实例没有 deposit 键,因此访问 account.deposit 时,元表的 __index 指向 Account,从而找到共享方法。

实例判断

可以为每个类维护独立元表,这样就能检查实例身份:

local function isInstance(value, class)
  local meta = getmetatable(value)
  return type(value) == "table" and meta == class
end

print(isInstance(account, Account)) -- true

如果有继承,需要沿原型链向上检查,或者用显式的类标记。

继承与多态

通过“类元表”的 __index 连接父类:

local Account = {}
Account.__index = Account

function Account.new(owner, balance)
  return setmetatable({
    owner = owner,
    balance = balance or 0
  }, Account)
end

function Account:withdraw(amount)
  if amount > self.balance then
    return nil, "insufficient balance"
  end
  self.balance = self.balance - amount
  return self.balance
end

local SavingsAccount = setmetatable({}, {
  __index = Account
})
SavingsAccount.__index = SavingsAccount

function SavingsAccount.new(owner, balance, minimum)
  local self = Account.new(owner, balance)
  self.minimum = minimum or 0
  return setmetatable(self, SavingsAccount)
end

function SavingsAccount:withdraw(amount)
  if self.balance - amount < self.minimum then
    return nil, "minimum balance required"
  end
  return Account.withdraw(self, amount)
end

SavingsAccount 的元表用于查找父类方法,SavingsAccount.__index 用于查找子类实例方法。调用父类方法时使用 Account.withdraw(self, amount),显式传入 self。

多态

local function withdrawAll(accountValue, amount)
  return accountValue:withdraw(amount)
end

local standard = Account.new("Ada", 100)
local savings = SavingsAccount.new("Bob", 100, 50)

print(withdrawAll(standard, 80))
print(withdrawAll(savings, 80))

调用者只依赖 withdraw 协议,不必知道具体类。继承适合稳定的“是一个”关系;仅为了复用几个函数时,组合通常更简单。

封装与访问控制

Lua 表字段默认公开。常见封装方式有三种:

闭包封装

local function createAccount(initial)
  local balance = initial

  return {
    getBalance = function()
      return balance
    end,
    withdraw = function(amount)
      if amount > balance then
        return nil, "insufficient balance"
      end
      balance = balance - amount
      return balance
    end
  }
end

闭包真正隐藏了状态,但每个对象都复制方法,也无法直接访问原型链。

弱约定

以下划线开头表示内部字段:

local account = {
  owner = "Ada",
  _balance = 100
}

这是最常见的 Lua 工程约定,依赖团队纪律而不是语言强制。

代理访问

用元表拦截字段读写,支持 getter、setter 和校验,但会增加间接层,调试时要查看真实数据所在位置。

设计取舍

在以下场景使用面向对象写法:

  • 对象有明确状态和生命周期。

  • 同一接口存在多个实现。

  • 需要复用稳定的行为层级。

在以下场景优先使用普通表和函数:

  • 数据只是配置或传输结构。

  • 函数没有共享状态。

  • 继承层级会很浅或根本不需要。

Lua 的优势是组合成本低。不要为了模拟其他语言而建立庞大的类层级。

上一章:元表与元方法 | 下一章:协程