面向对象编程
00:00
Lua OOP实现
概述
Lua 没有内置的类和继承机制,但通过元表和 __index 元方法可以优雅地实现面向对象编程。本文介绍 Lua 中类、继承、多态和封装的常见实现模式。
兯础概念
Lua OOP 的核心原理
- 表即对象:Lua 的表是唯一的复合数据结构,可以充当对象
- __index 实现继承:当表访问不存在的键时,__index 指向的表会被查找
- self 引用:使用冒号语法(:)自动传入 self 参数
- 原型链:通过 __index 链实现类似原型继承的效果
快速上手
基本类定义
-- 类定义模板
local Class = {}
Class.__index = Class
-- 构造函数
function Class.new(x, y)
-- setmetatable 将 Class 作为元表,__index 指向 Class
local self = setmetatable({}, Class)
self.x = x or 0
self.y = y or 0
return self
end
-- 实例方法(使用冒号语法,自动传入 self)
function Class:area()
return self.x * self.y
end
function Class:describe()
return string.format("矩形 %dx%d, 面积=%d", self.x, self.y, self:area())
end
-- 使用
local obj = Class.new(3, 4)
print(obj:area()) -- 12
print(obj:describe()) -- 矩形 3x4, 面积=12
继承
-- 基类
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:speak()
return self.name .. " 发出声音"
end
-- 子类
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name, breed)
local self = Animal.new(name) -- 调用父类构造函数
setmetatable(self, Dog) -- 设置子类元表
self.breed = breed
return self
end
-- 重写方法
function Dog:speak()
return self.name .. " 汪汪!"
end
-- 新增方法
function Dog:fetch(item)
return self.name .. " 捡回了 " .. item
end
-- 使用
local dog = Dog.new("旺财", "柴犬")
print(dog:speak()) -- 旺财 汪汪!
print(dog:fetch("球")) -- 旺财 捡回了 球
详细用法
通用类系统
-- 通用类创建函数
local function class(base)
local cls = {}
-- 如果有基类,设置继承
if base then
setmetatable(cls, { __index = base })
cls.super = base
end
cls.__index = cls
-- 通用构造函数
function cls.new(...)
local obj = setmetatable({}, cls)
if cls.init then cls.init(obj, ...) end
return obj
end
-- 支持创建子类
function cls:extend()
return class(self)
end
return cls
end
-- 使用
local Shape = class()
function Shape:init(name)
self.name = name
end
function Shape:area()
error("子类必须实现 area 方法")
end
function Shape:describe()
return string.format("%s: 面积=%.2f", self.name, self:area())
end
-- 创建子类
local Circle = Shape:extend()
function Circle:init(radius)
Shape.init(self, "圆形") -- 调用父类 init
self.radius = radius
end
function Circle:area()
return math.pi * self.radius ^ 2
end
-- 使用
local c = Circle.new(5)
print(c:describe()) -- 圆形: 面积=78.54
封装与私有性
-- 使用闭包实现私有变量
function BankAccount(initial)
local balance = initial or 0 -- 私有变量
local account = {}
function account:deposit(amount)
if amount <= 0 then error("金额必须为正数") end
balance = balance + amount
end
function account:withdraw(amount)
if amount > balance then error("余额不足") end
balance = balance - amount
end
function account:getBalance()
return balance
end
return account
end
-- 使用
local acc = BankAccount(100)
acc:deposit(50)
acc:withdraw(30)
print(acc:getBalance()) -- 120
-- print(acc.balance) -- nil,无法直接访问
多重继承
-- 多重继承
local function createClass(...)
local bases = { ... }
local cls = {}
cls.__index = cls
-- 在多个基类中查找方法
setmetatable(cls, {
__index = function(t, k)
for _, base in ipairs(bases) do
local v = base[k]
if v ~= nil then return v end
end
end
})
function cls.new(...)
local obj = setmetatable({}, cls)
if cls.init then cls.init(obj, ...) end
return obj
end
return cls
end
-- 定义混入(Mixin)
local Serializable = {
serialize = function(self)
local parts = {}
for k, v in pairs(self) do
if type(k) == "string" and not k:match("^_") then
table.insert(parts, k .. "=" .. tostring(v))
end
end
return "{" .. table.concat(parts, ", ") .. "}"
end
}
local Comparable = {
__lt = function(a, b) return a:value() < b:value() end,
__le = function(a, b) return a:value() <= b:value() end,
}
-- 使用多重继承
local Item = createClass(Serializable, Comparable)
常见场景
单例模式
-- Lua 单例实现
local Singleton = {}
Singleton.__index = Singleton
local instance = nil
function Singleton.getInstance()
if not instance then
instance = setmetatable({}, Singleton)
instance:init()
end
return instance
end
function Singleton:init()
self.data = {}
end
-- 使用
local s1 = Singleton.getInstance()
local s2 = Singleton.getInstance()
print(s1 == s2) -- true
观察者模式
-- 观察者模式
local Observable = {}
Observable.__index = Observable
function Observable.new()
return setmetatable({ listeners = {} }, Observable)
end
function Observable:on(event, callback)
if not self.listeners[event] then
self.listeners[event] = {}
end
table.insert(self.listeners[event], callback)
end
function Observable:emit(event, ...)
if self.listeners[event] then
for _, callback in ipairs(self.listeners[event]) do
callback(...)
end
end
end
function Observable:off(event, callback)
if self.listeners[event] then
for i, cb in ipairs(self.listeners[event]) do
if cb == callback then
table.remove(self.listeners[event], i)
break
end
end
end
end
-- 使用
local emitter = Observable.new()
emitter:on("data", function(data) print("收到数据: " .. data) end)
emitter:emit("data", "Hello")
注意事项
- Lua 的 OOP 是基于原型的,不是基于类的,理解 __index 的工作原理很重要
- 使用冒号语法(:)调用方法时,self 自动传入;使用点语法(.)需要手动传入
- 构造子类时记得调用父类的构造函数
- 闭包实现私有性会增加内存开销,每个实例都会创建新的函数
- __index 链过深会影响性能,避免过深的继承层级
- 使用 rawget 可以绕过 __index,直接访问表中存在的键
进阶用法
属性访问器
-- 使用 __index 和 __newindex 实现属性访问器
function createClassWithProps(props)
local cls = {}
cls.__index = function(t, k)
-- 先在实例中查找
local v = rawget(t, k)
if v ~= nil then return v end
-- 检查是否有 getter
local prop = props[k]
if prop and prop.get then return prop.get(t) end
-- 在类中查找方法
return cls[k]
end
cls.__newindex = function(t, k, v)
local prop = props[k]
if prop and prop.set then
prop.set(t, v) -- 通过 setter 设置
else
rawset(t, k, v) -- 直接设置
end
end
return cls
end
-- 使用
local Person = createClassWithProps({
fullName = {
get = function(self) return self.firstName .. " " .. self.lastName end,
set = function(self, v)
local parts = {}
for w in v:gmatch("%S+") do table.insert(parts, w) end
self.firstName = parts[1] or ""
self.lastName = parts[2] or ""
end
}
})
function Person.new(firstName, lastName)
local self = setmetatable({}, Person)
self.firstName = firstName
self.lastName = lastName
return self
end
local p = Person.new("张", "三")
print(p.fullName) -- "张 三"
p.fullName = "李 四"
print(p.firstName) -- "李"