面向对象编程
Lua 面向对象编程深度解析:原型继承、类系统设计、多重继承、封装与私有性、多态、设计模式、SOLID 原则在动态语言中的实践
面向对象编程
本文档对标 MIT 6.005 Software Construction、Stanford CS107 Programming Paradigms、CMU 15-214 Software Engineering 中面向对象设计与模式理论教学水准,面向 0 基础自学者与企业级 Lua 工程师,系统讲解 Lua 面向对象编程(OOP)的实现机制、原型继承(prototype-based inheritance)、类系统(class system)设计、多重继承(multiple inheritance)、封装与私有性(encapsulation)、多态(polymorphism)、设计模式(design patterns)、SOLID 原则在动态语言中的实践与多领域工程级案例。
1. 学习目标
学习本章后,读者应能在 Bloom 认知层级框架下达成下列目标。
1.1 知识层(Remembering)
- 列举面向对象编程的四大核心特征:封装(encapsulation)、继承(inheritance)、多态(polymorphism)、抽象(abstraction)。
- 复述 Lua 通过元表
__index实现原型继承的机制,以及冒号语法:与点语法.的差异。 - 列举 Lua 实现 OOP 的核心 API:
setmetatable、getmetatable、rawget、rawset、self隐式参数。 - 描述类(class)、对象(object/instance)、方法(method)、属性(property/field)在 Lua 中的对应:类即元表,对象即被该元表 setmetatable 的表,方法即元表字段,属性即对象字段。
- 列举经典设计模式的 Lua 实现:单例(Singleton)、工厂(Factory)、观察者(Observer)、策略(Strategy)、状态(State)、装饰器(Decorator)、代理(Proxy)、命令(Command)。
- 复述 SOLID 原则:单一职责(SRP)、开闭(OCP)、里氏替换(LSP)、接口隔离(ISP)、依赖倒置(DIP)。
1.2 理解层(Understanding)
- 解释 Lua 原型继承与基于类的继承(class-based inheritance)的本质差异:对象直接克隆 vs 类实例化。
- 阐释
self参数的工作原理:冒号语法自动传入调用对象作为第一个参数。 - 描述
__index元方法在方法查找中的作用:对象 → 类 → 父类 → … 的链式查找。 - 解释构造函数(constructor)的调用顺序:从子类向父类逐级调用
super.init。 - 描述方法重写(override)的机制:子类方法覆盖父类同名方法,通过
super显式调用父类版本。 - 解释 Lua 中”私有性”的实现方式:闭包封装、命名约定(下划线前缀)、代理表隔离。
- 描述多态在 Lua 中的鸭子类型(duck typing)实现:无需接口声明,运行时检查方法存在性。
- 解释 Mixin 模式:将独立功能模块混入类,通过多重继承组合能力。
1.3 应用层(Applying)
- 编写单继承类层次:基类定义公共方法,子类继承并重写特定方法。
- 使用闭包封装实现真正的私有变量,而非命名约定。
- 实现多重继承:多个基类的方法通过
__index链查找,处理方法冲突。 - 应用经典设计模式解决实际问题:观察者模式实现事件系统,策略模式实现算法切换。
- 使用 Mixin 模式组合功能:序列化 Mixin、比较 Mixin、事件 Mixin。
- 实现接口(interface)模拟:通过运行时检查方法存在性,提供类似 Java 接口的契约。
- 编写类工厂(class factory):动态生成类,支持配置驱动的设计。
- 应用 SOLID 原则重构 Lua 代码:拆分大函数为单一职责的类,通过依赖注入降低耦合。
1.4 分析层(Analyzing)
- 分析 Lua 原型继承与 JavaScript、Self、Io 等原型语言的异同:对象克隆机制、原型链查找、
__indexvs__proto__。 - 分析基于类的 OOP(Java、C++、Python)与基于原型的 OOP(Lua、JavaScript)的本质差异:类型与对象的二元关系。
- 分析 Lua 中静态类型检查的缺失对 OOP 的影响:鸭子类型的灵活性 vs 重构风险。
- 分析多重继承的”菱形问题”(diamond problem):不同基类有同名方法时的歧义性,Lua 的”首个匹配”策略与 C3 线性化的差异。
- 分析闭包封装私有性的内存开销:每个实例独立闭包,无法共享方法。
- 分析元表查找性能:深继承链的 O(L) 查找复杂度,以及 LuaJIT 的 specialization 优化。
- 区分抽象类(abstract class)、接口(interface)、Trait(Rust)在 Lua 中的可行实现与限制。
1.5 评价层(Evaluating)
- 评判 Lua OOP 设计的优劣:基于元表的灵活性 vs 缺乏语法支持的冗长。
- 评估闭包封装 vs 命名约定 vs 代理表三种私有性方案的权衡:安全性、性能、可读性。
- 评判多重继承的实用性:代码复用 vs 复杂性增加,何时该用 Mixin 而非多重继承。
- 评估鸭子类型在大型项目中的可维护性:重构时缺乏编译期保障,如何通过测试弥补。
- 评判 SOLID 原则在 Lua 中的适用性:动态语言中是否需要严格遵守,还是因语言特性调整。
- 评估 Lua OOP 与 Luau 类型系统结合的前景:渐进式类型如何提升 OOP 代码的可靠性。
1.6 创造层(Creating)
- 设计完整的类系统框架:支持构造、单/多重继承、抽象方法、接口检查、属性访问器、混入、序列化。
- 构建基于 OOP 的 ECS(Entity-Component-System)框架:实体、组件、系统的抽象设计。
- 设计事件驱动框架:基于观察者模式,支持事件订阅、过滤、优先级、批量触发。
- 构建状态机框架:基于状态模式,支持状态转换、守卫条件、嵌套状态、历史状态。
- 设计插件系统:基于 OOP 的扩展点(extension point)机制,支持运行时插件加载与卸载。
- 构建 ORM 框架:基于 OOP 的模型定义、关系映射、查询构造、事务管理。
2. 历史动机与演化
2.1 OOP 范式演化
面向对象编程历经四个主要阶段:
- Simula 阶段(1960s):Simula 67 引入类、对象、继承概念,用于仿真建模。
- Smalltalk 阶段(1970s-1980s):Smalltalk 确立”一切皆对象”哲学,消息传递机制,纯 OOP 语言。
- C++/Java 阶段(1980s-1990s):静态类型 OOP 成为主流,类、继承、多态、虚函数、接口标准化。
- 动态 OOP 与原型语言(1990s-):JavaScript、Lua、Python、Ruby 推动动态 OOP,原型继承、鸭子类型、元编程兴起。
Lua OOP 属于第四阶段,基于原型继承(prototype-based inheritance),与 JavaScript、Self 同源。Lua 选择原型继承的设计动机:
- 简洁性:无需引入新的语法结构(class 关键字),元表机制已足够表达继承。
- 一致性:表是唯一的复合数据结构,OOP 只是元表的用法之一,不破坏语言一致性。
- 可嵌入性:Lua 作为嵌入式脚本,不应强加 OOP 范式给宿主,提供机制而非策略。
- 教学简洁:原型继承比类继承更直观(对象克隆对象),降低学习门槛。
2.2 Lua OOP 的诞生动机
Lua 3.1(1998)引入 __index 元方法,为 OOP 提供基础。其设计动机:
- 代码复用:大型 Lua 项目需要继承机制减少重复代码,
__index链实现方法复用。 - 抽象数据类型:游戏对象(WoW)、UI 组件(Love2D)、配置对象需要 OOP 抽象。
- C API 桥接:C 扩展通过元表暴露”对象”给 Lua,OOP 模式使 Lua 端使用更自然。
- 设计模式:Lua 社区需要经典设计模式(观察者、单例等)解决工程问题,OOP 是基础。
- 跨语言互操作:与 Java(WoW)、Python(OpenResty)、C#(Roblox)等 OOP 语言交互时,OOP 模式更自然。
2.3 演化时间线
| 版本/年份 | OOP 相关变化 |
|---|---|
| Lua 1.0(1993) | 无元表,无 OOP 支持,仅函数与表 |
| Lua 3.0(1997) | 引入元表,但无 __index,OOP 受限 |
| Lua 3.1(1998) | 引入 __index、__newindex,原型继承成为可能 |
| Lua 4.0(2000) | 元方法 API 稳定,OOP 模式开始流行 |
| Lua 5.0(2003) | setmetatable/getmetatable 引入,OOP API 标准化 |
| Lua 5.1(2006) | WoW、Redis、OpenResty 广泛采用 Lua OOP 模式 |
| Lua 5.2(2011) | __pairs 引入,支持自定义遍历的 OOP 对象 |
| Lua 5.3(2015) | __name 元方法,OOP 对象错误信息更友好 |
| Lua 5.4(2020) | __gc 支持表,OOP 对象可定义析构函数;<close> 用于资源管理 |
| LuaJIT(2011-) | 兼容 Lua 5.1 OOP,JIT 编译器对元表链 specialization 优化 |
| Luau(2021-) | Roblox 方言,渐进式类型系统与 OOP 结合,export type 支持类接口 |
| 中间件框架 | OpenResty、Lapis、Turbo.lua 推广 Lua OOP 在 Web 后端的应用 |
| 游戏脚本 | WoW、Roblox、Love2D 等游戏将 Lua OOP 作为标准脚本组织方式 |
2.4 设计动机总结
Lua OOP 设计遵循以下原则:
- 机制而非策略:语言提供元表机制,具体 OOP 模式由库与开发者决定。
- 鸭子类型:无接口声明,运行时检查方法存在性,灵活但需测试保障。
- 原型优先:对象直接克隆对象,类只是惯例(元表模式),非语言强制。
- 可选 OOP:Lua 代码可不使用 OOP,函数式、过程式均可,不强制范式。
- 零开销抽象:不用 OOP 时无性能损失,用 OOP 时元表查找开销可控。
- 可扩展:开发者可构建自己的类系统,如 middleclass、LOOP、LuaOOP 等库。
3. 形式化定义
3.1 对象的代数模型
Lua 对象 是一个表,其字段包括属性(property)与方法(method)。方法通过元表 提供:
其中:
- 是属性映射,存储于对象本身。
- 是方法映射,存储于元表。
对象 的元表为 :,(类自身的 __index 指向自身)。
3.2 类的形式化
类 是一个特殊的表,既是元表又是方法容器:
其中:
- 是方法集合。
- 是构造函数,创建对象 并设置 。
构造函数语义:
3.3 原型继承的形式化
原型继承(prototype-based inheritance):对象 的方法查找通过元表链进行:
若 (类),,则 。
继承:子类 继承父类 ,通过设置 :
方法查找沿继承链递归:
3.4 self 参数的形式化
冒号语法 obj:method(args) 等价于 obj.method(obj, args),self 自动绑定:
定义方法时,function C:method(args) ... end 等价于 function C.method(self, args) ... end:
3.5 多态的形式化
多态(polymorphism):不同对象对同一消息(方法名)作出不同响应。
设对象 与 均响应方法 ,但实现不同:
调用 根据对象类型分派:
Lua 多态基于鸭子类型:无需声明接口,运行时检查方法存在性。
3.6 封装的形式化
封装(encapsulation):隐藏对象内部状态,仅通过方法访问。
Lua 无原生 private 关键字,通过以下方式模拟:
- 闭包封装:对象工厂函数返回闭包,内部变量不可外部访问:
-
命名约定:以
_前缀标记”私有”字段,运行时不强制,仅约定。 -
代理表隔离:外层代理表拦截所有访问,内层数据表存储真实状态。
3.7 多重继承的形式化
多重继承:类 继承多个基类 :
方法查找:
注意:Lua 采用”首个匹配”策略,与 Python C3 线性化不同,可能导致菱形问题。
3.8 抽象类的形式化
抽象类(abstract class):包含抽象方法(未实现)的类,子类必须实现:
子类 继承 后必须重写 中所有方法:
4. 理论推导与证明
4.1 原型继承与类继承的等价性
命题 1:原型继承与类继承在表达能力上等价。
证明:
(1) 类继承可由原型继承模拟:
设类继承中类 继承 ,实例 是 的实例。在原型继承中,设 的原型为 , 的原型为 :
- 查找方法 :先查 ,再查 ,再查 。
- 等价于类继承中 查找 :先查实例字段,再查类 ,再查父类 。
(2) 原型继承可由类继承模拟:
设原型继承中 克隆自 。在类继承中,设 是”类”, 是其实例:
- 查找方法 :先查 ,再查 (作为类)。
- 等价于原型继承中 查找 :先查 ,再查原型 。
故两者表达能力等价。但语义上有差异:
- 类继承:类是”模板”,实例是”产品”,二者角色明确。
- 原型继承:对象直接克隆对象,无类与实例的严格区分。
证毕。
4.2 方法查找复杂度
命题 2:单继承方法查找的最坏时间复杂度为 ,其中 为继承深度。
证明:
设继承链 ( 是子类, 是根类),对象 的元表为 。
查找 :
- 查 :若存在,返回;否则进入 。
- 查 :若存在,返回;否则进入 。
- …
- 查 :若存在,返回;否则返回 nil。
最坏情况下, 不存在于任何 ,需遍历整条链,共 次查找。每次查找 (哈希表),故总复杂度 。
推论:深继承链()会显著拖慢热路径,应避免。
证毕。
4.3 多重继承的菱形问题
命题 3:Lua 多重继承采用”首个匹配”策略,可能产生菱形问题。
证明:
设类层次:
A
/ \
B C
\ /
D
定义方法 , 与 均继承 但未重写 , 多重继承 与 。
Lua 查找 :
- 查 :不存在。
- 查 :不存在( 未重写)。
- 查 :不存在( 未重写)。
- 返回 nil。
问题: 的 未被查到,因为 Lua 的”首个匹配”仅查 与 的直接字段,不递归到 。
解决方案:多重继承的 __index 函数应递归查找基类的继承链:
__index = function(t, k)
for _, base in ipairs(bases) do
local v = base[k] -- 触发 base 的 __index,递归查找
if v ~= nil then return v end
end
end
注意:base[k] 触发 base 的 __index,递归到 ,正确返回 。
Python 的 C3 线性化通过预先计算方法解析顺序(MRO)避免菱形问题,Lua 需开发者手动处理。
证毕。
4.4 闭包封装的安全性
命题 4:闭包封装提供真正的私有性,外部代码无法访问内部状态。
证明:
闭包封装结构:
function createAccount(initial)
local balance = initial -- 闭包变量,外部不可访问
return {
deposit = function(amount) balance = balance + amount end,
getBalance = function() return balance end,
}
end
外部代码:
local acc = createAccount(100)
acc.deposit(50)
print(acc.getBalance()) -- 150
print(acc.balance) -- nil,无法直接访问
acc.balance = 1000000 -- 设置新字段,但不影响内部 balance
print(acc.getBalance()) -- 仍为 150,内部状态未被破坏
内部 balance 是闭包变量,不在返回的对象表中,外部无法通过任何方式访问(除非通过 debug.getupvalue 调试 API)。
代价:每个实例的 deposit、getBalance 方法是独立闭包,不共享,内存开销大于元表共享方法。
证毕。
4.5 鸭子类型的类型安全
命题 5:Lua 鸭子类型提供运行时类型检查,但无编译期保障。
证明:
鸭子类型:“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。”
Lua 代码:
function feed(animal)
animal:eat("food") -- 不检查 animal 的类型,仅检查 eat 方法存在
end
local duck = { eat = function(self, food) print("duck eats " .. food) end }
local dog = { eat = function(self, food) print("dog eats " .. food) end }
local robot = { charge = function(self) end }
feed(duck) -- duck eats food
feed(dog) -- dog eats food
feed(robot) -- error: attempt to call a nil value (method 'eat')
优势:灵活,任何具有 eat 方法的对象均可被 feed,无需声明接口。
劣势:运行时才发现错误,重构时(如重命名 eat 为 consume)无编译期提示。
弥补:通过单元测试覆盖,或使用 Luau 渐进式类型系统添加类型注解。
证毕。
4.6 构造函数调用顺序
命题 6:子类构造函数应显式调用父类构造函数,顺序为”父类先,子类后”。
证明:
设类层次 ( 继承 ),对象 是 的实例。
正确构造顺序:
function S.new(...)
local self = setmetatable({}, S)
B.init(self, ...) -- 先调用父类 init,初始化父类字段
S.init(self, ...) -- 再调用子类 init,初始化子类字段
return self
end
顺序分析:
B.init设置 的父类字段(如name、age)。S.init设置 的子类字段(如breed、tricks),可能依赖父类字段。
若顺序颠倒(子类先):
S.init可能依赖父类字段,但父类字段未初始化,导致 nil 错误。
故正确顺序为”父类先,子类后”。
替代方案:统一使用 init 方法,子类 init 内部首行调用 B.init(self, ...):
function S:init(...)
B.init(self, ...) -- 首行调用父类 init
-- 子类初始化
end
证毕。
5. 代码示例
5.1 基本类定义
-- lua: 基本类定义模板
local Animal = {}
Animal.__index = Animal
Animal.__name = "Animal" -- Lua 5.3+ 用于错误信息
-- 构造函数
function Animal.new(name, age)
local self = setmetatable({}, Animal)
self.name = name or "unknown"
self.age = age or 0
return self
end
-- 实例方法(冒号语法)
function Animal:speak()
return self.name .. " makes a sound"
end
function Animal:describe()
return string.format("%s is %d years old", self.name, self.age)
end
function Animal:birthday()
self.age = self.age + 1
return self.age
end
-- 静态方法(点语法)
function Animal.createDefault()
return Animal.new("Anonymous", 1)
end
-- 使用
local a = Animal.new("Rex", 5)
print(a:speak()) -- Rex makes a sound
print(a:describe()) -- Rex is 5 years old
print(a:birthday()) -- 6
local b = Animal.createDefault()
print(b:describe()) -- Anonymous is 1 years old
5.2 单继承
-- lua: 单继承示例
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 .. " makes a sound"
end
function Animal:eat(food)
return self.name .. " eats " .. food
end
-- Dog 继承 Animal
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
Dog.__name = "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 .. " barks"
end
-- 新增方法
function Dog:fetch(item)
return self.name .. " fetches " .. item
end
-- 调用父类方法(通过 super 模式)
function Dog:describe()
-- 显式调用父类方法
local base = getmetatable(Dog).__index -- 获取 Animal
return base.describe and base:describe(self) or (self.name .. " is a " .. self.breed)
end
-- 使用
local d = Dog.new("Rex", "Labrador")
print(d:speak()) -- Rex barks(重写)
print(d:eat("meat")) -- Rex eats meat(继承)
print(d:fetch("ball")) -- Rex fetches ball(新增)
print(d:describe()) -- Rex is a Labrador
5.3 通用类系统
-- lua: 通用类系统,支持继承、构造、init
local function class(base, name)
local cls = {}
cls.__name = name or "class"
-- 继承
if base then
setmetatable(cls, { __index = base })
cls._base = base
end
cls.__index = cls
-- 通用构造函数
function cls.new(...)
local obj = setmetatable({}, cls)
if obj.init then obj:init(...) end
return obj
end
-- 创建子类
function cls:extend(name)
return class(self, name)
end
-- 类型检查:obj 是否是 cls 或其子类的实例
function cls:isInstance(obj)
local mt = getmetatable(obj)
while mt do
if mt == self then return true end
mt = getmetatable(mt) and getmetatable(mt).__index
end
return false
end
-- super 方法:获取父类
function cls:super()
return self._base
end
return cls
end
-- 使用
local Shape = class(nil, "Shape")
function Shape:init(name)
self.name = name
end
function Shape:area()
error("abstract method: Shape:area must be overridden", 2)
end
function Shape:describe()
return string.format("%s: area = %.2f", self.name, self:area())
end
local Circle = Shape:extend("Circle")
function Circle:init(radius)
Shape.init(self, "Circle") -- 调用父类 init
self.radius = radius
end
function Circle:area()
return math.pi * self.radius ^ 2
end
local Rectangle = Shape:extend("Rectangle")
function Rectangle:init(w, h)
Shape.init(self, "Rectangle")
self.w, self.h = w, h
end
function Rectangle:area()
return self.w * self.h
end
-- 多态
local shapes = {
Circle.new(5),
Rectangle.new(3, 4),
}
for _, s in ipairs(shapes) do
print(s:describe())
end
-- Circle: area = 78.54
-- Rectangle: area = 12.00
-- 类型检查
print(Circle:isInstance(shapes[1])) -- true
print(Rectangle:isInstance(shapes[1])) -- false
print(Shape:isInstance(shapes[1])) -- true(子类实例也是父类实例)
5.4 闭包封装私有性
-- lua: 闭包封装实现真正的私有变量
local function createBankAccount(initialBalance, owner)
local balance = initialBalance or 0 -- 私有变量
local transactions = {} -- 私有变量
local ownerName = owner or "anonymous"
-- 私有方法
local function recordTransaction(type_, amount)
table.insert(transactions, {
type = type_,
amount = amount,
time = os.time(),
})
end
-- 公开方法(返回的对象)
return {
deposit = function(amount)
assert(amount > 0, "deposit must be positive")
balance = balance + amount
recordTransaction("deposit", amount)
return balance
end,
withdraw = function(amount)
assert(amount > 0, "withdraw must be positive")
assert(amount <= balance, "insufficient balance")
balance = balance - amount
recordTransaction("withdraw", amount)
return balance
end,
getBalance = function()
return balance
end,
getOwner = function()
return ownerName
end,
getHistory = function()
-- 返回副本,防止外部修改
local copy = {}
for i, t in ipairs(transactions) do
copy[i] = { type = t.type, amount = t.amount, time = t.time }
end
return copy
end,
}
end
-- 使用
local acc = createBankAccount(1000, "Alice")
acc.deposit(500)
acc.withdraw(200)
print(acc.getBalance()) -- 1300
print(acc.getOwner()) -- Alice
-- print(acc.balance) -- nil,无法访问
-- acc.balance = 99999 -- 无效,内部 balance 不变
print(acc.getBalance()) -- 仍为 1300
local history = acc.getHistory()
for _, t in ipairs(history) do
print(t.type, t.amount)
end
5.5 多重继承与 Mixin
-- lua: 多重继承与 Mixin 模式
local function createClass(...)
local bases = { ... }
local cls = {}
cls.__index = cls
-- 设置 __index 在多基类中查找
setmetatable(cls, {
__index = function(_, k)
for _, base in ipairs(bases) do
local v = base[k] -- 触发 base 的 __index,递归查找
if v ~= nil then return v end
end
return nil
end,
})
function cls.new(...)
local obj = setmetatable({}, cls)
if obj.init then obj:init(...) end
return obj
end
return cls
end
-- Mixin 1: 序列化
local Serializable = {}
function Serializable:serialize()
local parts = {}
for k, v in pairs(self) do
if type(k) == "string" and not k:match("^_") then
table.insert(parts, string.format("%s=%s", k, tostring(v)))
end
end
return "{" .. table.concat(parts, ", ") .. "}"
end
function Serializable:toJson()
local parts = {}
for k, v in pairs(self) do
if type(k) == "string" and not k:match("^_") then
local val
if type(v) == "string" then val = '"' .. v .. '"'
elseif type(v) == "number" or type(v) == "boolean" then val = tostring(v)
else val = "null" end
table.insert(parts, string.format('"%s":%s', k, val))
end
end
return "{" .. table.concat(parts, ",") .. "}"
end
-- Mixin 2: 比较
local Comparable = {}
function Comparable.__lt(a, b) return a:value() < b:value() end
function Comparable.__le(a, b) return a:value() <= b:value() end
function Comparable.__eq(a, b) return a:value() == b:value() end
-- Mixin 3: 事件
local Observable = {}
function Observable:init()
self._listeners = {}
end
function Observable:on(event, callback)
self._listeners[event] = self._listeners[event] or {}
table.insert(self._listeners[event], callback)
end
function Observable:emit(event, ...)
if self._listeners[event] then
for _, cb in ipairs(self._listeners[event]) do
cb(...)
end
end
end
-- 派生类:组合多个 Mixin
local Score = createClass(Serializable, Comparable, Observable)
function Score:init(value)
self._value = value
Observable.init(self) -- 调用 Mixin 的 init
end
function Score:value() return self._value end
-- 使用
local s1 = Score.new(85)
local s2 = Score.new(92)
print(s1:serialize()) -- {_value=85}
print(s1 < s2) -- true
s1:on("change", function(new) print("changed to " .. new) end)
s1:emit("change", 90)
5.6 抽象类与接口模拟
-- lua: 抽象类与接口模拟
-- 抽象类:包含抽象方法,子类必须实现
local function abstractMethod(name)
return function()
error("abstract method '" .. name .. "' must be implemented", 2)
end
end
local Shape = {}
Shape.__index = Shape
Shape.__name = "Shape"
function Shape.new(name)
error("Shape is abstract, cannot be instantiated directly", 2)
end
function Shape:area()
return abstractMethod("Shape:area")()
end
function Shape:perimeter()
return abstractMethod("Shape:perimeter")()
end
function Shape:describe()
return string.format("%s: area=%.2f, perimeter=%.2f",
self.__name or "Shape", self:area(), self:perimeter())
end
-- 子类必须实现 area 与 perimeter
local Circle = setmetatable({}, { __index = Shape })
Circle.__index = Circle
Circle.__name = "Circle"
function Circle.new(radius)
local self = setmetatable({}, Circle)
self.radius = radius
return self
end
function Circle:area() return math.pi * self.radius ^ 2 end
function Circle:perimeter() return 2 * math.pi * self.radius end
-- 接口模拟:运行时检查方法存在性
local function implements(obj, ...)
local required = { ... }
for _, method in ipairs(required) do
if type(obj[method]) ~= "function" then
return false, "missing method: " .. method
end
end
return true
end
-- 使用
local c = Circle.new(5)
print(c:describe()) -- Circle: area=78.54, perimeter=31.42
-- local s = Shape.new("test") -- error: Shape is abstract
print(implements(c, "area", "perimeter", "describe")) -- true
print(implements(c, "nonexistent")) -- false, missing method: nonexistent
5.7 单例模式
-- lua: 单例模式的多种实现
-- 方式 1:模块级单例(最简单)
local Singleton = {}
Singleton.__index = Singleton
Singleton._instance = nil
function Singleton.getInstance()
if not Singleton._instance then
Singleton._instance = setmetatable({
data = {},
count = 0,
}, Singleton)
end
return Singleton._instance
end
function Singleton:add(item)
self.count = self.count + 1
table.insert(self.data, item)
end
-- 使用
local s1 = Singleton.getInstance()
local s2 = Singleton.getInstance()
print(s1 == s2) -- true,同一实例
s1:add("a")
print(s2.count) -- 1
-- 方式 2:闭包单例(更隔离)
local function createSingleton()
local instance = nil
return function()
if not instance then
instance = {
data = {},
count = 0,
add = function(self, item)
self.count = self.count + 1
table.insert(self.data, item)
end,
}
end
return instance
end
end
local getSingleton = createSingleton()
local s3 = getSingleton()
local s4 = getSingleton()
print(s3 == s4) -- true
5.8 观察者模式
-- lua: 观察者模式实现事件系统
local EventEmitter = {}
EventEmitter.__index = EventEmitter
function EventEmitter.new()
return setmetatable({
_listeners = {}, -- event -> {callback list}
_onceListeners = {}, -- 一次性监听器
}, EventEmitter)
end
function EventEmitter:on(event, callback)
self._listeners[event] = self._listeners[event] or {}
table.insert(self._listeners[event], callback)
return self -- 链式调用
end
function EventEmitter:once(event, callback)
self._onceListeners[event] = self._onceListeners[event] or {}
table.insert(self._onceListeners[event], callback)
return self
end
function EventEmitter:off(event, callback)
local listeners = self._listeners[event]
if listeners then
for i, cb in ipairs(listeners) do
if cb == callback then
table.remove(listeners, i)
break
end
end
end
return self
end
function EventEmitter:emit(event, ...)
-- 触发普通监听器
if self._listeners[event] then
for _, cb in ipairs(self._listeners[event]) do
cb(...)
end
end
-- 触发一次性监听器并清除
if self._onceListeners[event] then
for _, cb in ipairs(self._onceListeners[event]) do
cb(...)
end
self._onceListeners[event] = nil
end
return self
end
function EventEmitter:removeAllListeners(event)
if event then
self._listeners[event] = nil
self._onceListeners[event] = nil
else
self._listeners = {}
self._onceListeners = {}
end
return self
end
-- 使用
local emitter = EventEmitter.new()
emitter:on("data", function(d) print("got data: " .. d) end)
emitter:once("init", function() print("initialized") end)
emitter:emit("init") -- initialized
emitter:emit("init") -- 无输出(once 已清除)
emitter:emit("data", "hello") -- got data: hello
5.9 策略模式
-- lua: 策略模式:算法族,封装可互换
local Sorter = {}
Sorter.__index = Sorter
function Sorter.new(strategy)
return setmetatable({
strategy = strategy or Sorter.strategies.bubble,
}, Sorter)
end
Sorter.strategies = {
bubble = function(arr)
for i = 1, #arr - 1 do
for j = 1, #arr - i do
if arr[j] > arr[j + 1] then
arr[j], arr[j + 1] = arr[j + 1], arr[j]
end
end
end
end,
quick = function(arr)
local function partition(low, high)
local pivot = arr[high]
local i = low - 1
for j = low, high - 1 do
if arr[j] <= pivot then
i = i + 1
arr[i], arr[j] = arr[j], arr[i]
end
end
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
end
local function quickSort(low, high)
if low < high then
local p = partition(low, high)
quickSort(low, p - 1)
quickSort(p + 1, high)
end
end
quickSort(1, #arr)
end,
insert = function(arr)
for i = 2, #arr do
local key = arr[i]
local j = i - 1
while j >= 1 and arr[j] > key do
arr[j + 1] = arr[j]
j = j - 1
end
arr[j + 1] = key
end
end,
}
function Sorter:sort(arr)
self.strategy(arr)
return arr
end
function Sorter:setStrategy(strategy)
self.strategy = strategy
end
-- 使用
local data = {5, 2, 8, 1, 9, 3}
local sorter = Sorter.new(Sorter.strategies.bubble)
print(table.concat(sorter:sort({5, 2, 8, 1, 9, 3}), ", ")) -- 1, 2, 3, 5, 8, 9
sorter:setStrategy(Sorter.strategies.quick)
print(table.concat(sorter:sort({5, 2, 8, 1, 9, 3}), ", ")) -- 1, 2, 3, 5, 8, 9
5.10 状态模式
-- lua: 状态模式:对象行为随状态变化
local TrafficLight = {}
TrafficLight.__index = TrafficLight
-- 状态定义
local states = {
red = {
color = "red",
next = "green",
action = function() return "stop" end,
duration = 60,
},
green = {
color = "green",
next = "yellow",
action = function() return "go" end,
duration = 55,
},
yellow = {
color = "yellow",
next = "red",
action = function() return "caution" end,
duration = 5,
},
}
function TrafficLight.new()
return setmetatable({
state = states.red,
}, TrafficLight)
end
function TrafficLight:change()
self.state = states[self.state.next]
return self.state.color
end
function TrafficLight:action()
return self.state.action()
end
function TrafficLight:color()
return self.state.color
end
-- 使用
local light = TrafficLight.new()
print(light:color()) -- red
print(light:action()) -- stop
print(light:change()) -- green
print(light:action()) -- go
print(light:change()) -- yellow
print(light:action()) -- caution
print(light:change()) -- red
5.11 装饰器模式
-- lua: 装饰器模式:动态添加职责
local Coffee = {}
Coffee.__index = Coffee
function Coffee.new()
return setmetatable({}, Coffee)
end
function Coffee:cost() return 5 end
function Coffee:description() return "Coffee" end
-- 装饰器基类
local CoffeeDecorator = {}
CoffeeDecorator.__index = CoffeeDecorator
function CoffeeDecorator.new(coffee)
return setmetatable({
coffee = coffee,
}, CoffeeDecorator)
end
function CoffeeDecorator:cost()
return self.coffee:cost()
end
function CoffeeDecorator:description()
return self.coffee:description()
end
-- 具体装饰器:MilkDecorator
local MilkDecorator = setmetatable({}, { __index = CoffeeDecorator })
MilkDecorator.__index = MilkDecorator
function MilkDecorator.new(coffee)
return setmetatable(CoffeeDecorator.new(coffee), MilkDecorator)
end
function MilkDecorator:cost()
return self.coffee:cost() + 2
end
function MilkDecorator:description()
return self.coffee:description() .. ", Milk"
end
-- 具体装饰器:SugarDecorator
local SugarDecorator = setmetatable({}, { __index = CoffeeDecorator })
SugarDecorator.__index = SugarDecorator
function SugarDecorator.new(coffee)
return setmetatable(CoffeeDecorator.new(coffee), SugarDecorator)
end
function SugarDecorator:cost()
return self.coffee:cost() + 1
end
function SugarDecorator:description()
return self.coffee:description() .. ", Sugar"
end
-- 使用
local coffee = Coffee.new()
print(coffee:description(), coffee:cost()) -- Coffee 5
local milkCoffee = MilkDecorator.new(coffee)
print(milkCoffee:description(), milkCoffee:cost()) -- Coffee, Milk 7
local sweetMilkCoffee = SugarDecorator.new(milkCoffee)
print(sweetMilkCoffee:description(), sweetMilkCoffee:cost()) -- Coffee, Milk, Sugar 8
5.12 命令模式
-- lua: 命令模式:将请求封装为对象
local Command = {}
Command.__index = Command
function Command.new(execute, undo, description)
return setmetatable({
execute = execute,
undo = undo,
description = description or "",
}, Command)
end
-- 调用者:命令历史
local Invoker = {}
Invoker.__index = Invoker
function Invoker.new()
return setmetatable({
history = {},
undone = {},
}, Invoker)
end
function Invoker:execute(cmd)
cmd.execute()
table.insert(self.history, cmd)
self.undone = {} -- 清空 redo 栈
end
function Invoker:undo()
local cmd = table.remove(self.history)
if cmd then
cmd.undo()
table.insert(self.undone, cmd)
return true
end
return false
end
function Invoker:redo()
local cmd = table.remove(self.undone)
if cmd then
cmd.execute()
table.insert(self.history, cmd)
return true
end
return false
end
-- 使用:文本编辑器
local text = ""
local invoker = Invoker.new()
local function makeInsertCommand(str)
return Command.new(
function() text = text .. str end,
function() text = text:sub(1, #text - #str) end,
"insert '" .. str .. "'"
)
end
invoker:execute(makeInsertCommand("Hello"))
invoker:execute(makeInsertCommand(", "))
invoker:execute(makeInsertCommand("World!"))
print(text) -- Hello, World!
invoker:undo()
print(text) -- Hello,
invoker:redo()
print(text) -- Hello, World!
5.13 工厂模式
-- lua: 工厂模式:封装对象创建
local Enemy = {}
Enemy.__index = Enemy
function Enemy.new(type_, level)
local cls = Enemy.types[type_]
if not cls then error("unknown enemy type: " .. type_, 2) end
return cls.new(level)
end
Enemy.types = {}
-- 注册类型
function Enemy.register(type_, cls)
Enemy.types[type_] = cls
end
-- 具体类型:Goblin
local Goblin = setmetatable({}, { __index = Enemy })
Goblin.__index = Goblin
Goblin.__name = "Goblin"
function Goblin.new(level)
return setmetatable({
type = "Goblin",
level = level,
hp = 50 + level * 10,
attack = 5 + level * 2,
}, Goblin)
end
function Goblin:describe()
return string.format("Goblin(L%d) HP=%d ATK=%d", self.level, self.hp, self.attack)
end
-- 具体类型:Dragon
local Dragon = setmetatable({}, { __index = Enemy })
Dragon.__index = Dragon
Dragon.__name = "Dragon"
function Dragon.new(level)
return setmetatable({
type = "Dragon",
level = level,
hp = 200 + level * 50,
attack = 20 + level * 5,
}, Dragon)
end
function Dragon:describe()
return string.format("Dragon(L%d) HP=%d ATK=%d", self.level, self.hp, self.attack)
end
-- 注册
Enemy.register("goblin", Goblin)
Enemy.register("dragon", Dragon)
-- 使用
local e1 = Enemy.new("goblin", 3)
local e2 = Enemy.new("dragon", 5)
print(e1:describe()) -- Goblin(L3) HP=80 ATK=11
print(e2:describe()) -- Dragon(L5) HP=450 ATK=45
5.14 属性访问器
-- lua: 属性访问器:精细控制读写
local 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 设置
elseif prop and not prop.set then
error("property '" .. k .. "' is read-only", 2)
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,
},
age = {
get = function(self) return self._age end,
set = function(self, v)
assert(v >= 0 and v <= 150, "invalid age")
self._age = v
end,
},
id = {
get = function(self) return self._id end,
-- 无 set,只读
},
})
function Person.new(firstName, lastName, age, id)
local self = setmetatable({}, Person)
self.firstName = firstName
self.lastName = lastName
self.age = age
self._id = id
return self
end
-- 使用
local p = Person.new("Alice", "Smith", 30, "P001")
print(p.fullName) -- Alice Smith
print(p.age) -- 30
print(p.id) -- P001
p.fullName = "Bob Jones"
print(p.firstName, p.lastName) -- Bob Jones
-- p.id = "P002" -- error: property 'id' is read-only
-- p.age = -1 -- error: invalid age
5.15 代理模式
-- lua: 代理模式:控制对象访问
local function createLazyProxy(factory)
local real = nil
return setmetatable({}, {
__index = function(_, k)
if not real then
real = factory()
end
return real[k]
end,
__newindex = function(_, k, v)
if not real then
real = factory()
end
real[k] = v
end,
__call = function(_, ...)
if not real then
real = factory()
end
return real(...)
end,
})
end
-- 使用:惰性加载重型对象
local heavyService = createLazyProxy(function()
print("initializing heavy service...")
return {
getData = function() return "data" end,
processData = function(d) return "processed:" .. d end,
}
end)
print("proxy created, but not initialized")
print(heavyService.getData()) -- 此时才初始化
-- 输出:
-- proxy created, but not initialized
-- initializing heavy service...
-- data
6. 对比分析
6.1 与 JavaScript OOP 对比
| 维度 | Lua OOP | JavaScript OOP |
|---|---|---|
| 继承机制 | 元表 __index 链 | [[Prototype]] 链 |
| 类语法 | 无(基于元表惯例) | class 关键字(ES6+) |
this/self | self(冒号语法自动传入) | this(调用上下文绑定) |
| 私有性 | 闭包或命名约定 | # 私有字段(ES2022+) |
| 多重继承 | 支持(__index 函数形态) | 不支持(mixin 模式) |
| 类型检查 | 鸭子类型 | 鸭子类型 + TypeScript |
| 静态方法 | 类表字段 | static 关键字 |
| 构造函数 | 自定义 new 函数 | constructor 方法 |
| 性能 | 元表查找开销 | V8 隐藏类优化 |
Lua 与 JavaScript 同为原型语言,但 JavaScript 在 ES6 后提供 class 语法糖,而 Lua 始终基于元表惯例。
6.2 与 Python OOP 对比
| 维度 | Lua OOP | Python OOP |
|---|---|---|
| 类语法 | 无(元表惯例) | class 关键字 |
| 继承机制 | 元表链 | 类 MRO(C3 线性化) |
| 多重继承 | 支持(首个匹配) | 支持(C3 MRO) |
| 私有性 | 闭包或约定 | _/__ 命名约定 |
| 接口 | 鸭子类型 | ABC + isinstance |
| 静态方法 | 类表字段 | @staticmethod |
| 类方法 | 类表字段 | @classmethod |
| 属性访问器 | __index/__newindex | @property |
| 元编程 | 元方法 | __getattr__/__setattr__ |
Python 提供更丰富的 OOP 语法糖,但 Lua 的元表机制更灵活、可定制。
6.3 与 Java OOP 对比
| 维度 | Lua OOP | Java OOP |
|---|---|---|
| 类型系统 | 动态 | 静态 |
| 类语法 | 无 | class 关键字 |
| 继承 | 单继承 + Mixin | 单继承 + 接口 |
| 多态 | 鸭子类型 | 接口 + 虚方法 |
| 访问控制 | 无 | private/protected/public |
| 抽象类 | 模拟(错误抛出) | abstract 关键字 |
| 接口 | 模拟(运行时检查) | interface 关键字 |
| 重写 | 约定 | @Override |
| 性能 | 元表查找 | 虚方法分派(JIT 优化) |
| 工具支持 | 弱 | 强(IDE 重构、导航) |
Java 提供完整的 OOP 类型系统与工具链;Lua 以灵活性换取简洁,适合快速原型。
6.4 与 C++ OOP 对比
| 维度 | Lua OOP | C++ OOP |
|---|---|---|
| 类型系统 | 动态 | 静态 |
| 内存管理 | GC | 手动 / RAII |
| 继承 | 单/Multiple | 单/多/虚继承 |
| 多态 | 鸭子类型 | 虚函数(vtable) |
| 模板 | 无 | template |
| 运算符重载 | 元方法 | operator+ 等 |
| 性能 | 元表查找 | 编译期绑定,零开销 |
| RTTI | 弱 | typeid/dynamic_cast |
C++ 追求性能与静态安全;Lua 追求灵活与简洁。
6.5 与 Rust Trait 对比
| 维度 | Lua OOP | Rust Trait |
|---|---|---|
| 类型系统 | 动态 | 静态 |
| 继承 | 原型继承 | 无继承,Trait 组合 |
| 多态 | 鸭子类型 | Trait object / 泛型 |
| 抽象 | 命名约定 | Trait |
| 默认实现 | Mixin | Trait 默认方法 |
| 性能 | 元表查找 | 静态分派 / 动态分派 |
| 安全 | 无 | 编译期保障 |
Rust 用 Trait 替代继承,避免菱形问题;Lua 用 Mixin 模式实现类似效果,但无编译期保障。
7. 常见陷阱与反模式
7.1 忘记设置 __index 指向自身
-- lua: 错误示例
local BadClass = {}
-- 忘记 BadClass.__index = BadClass
function BadClass.new()
return setmetatable({}, BadClass)
end
function BadClass:method() return "hello" end
local obj = BadClass.new()
-- print(obj:method()) -- error: attempt to call a nil value (method 'method')
-- 因为 obj 的元表是 BadClass,但 BadClass.__index 未设置,obj.method 查找失败
-- 正确
local GoodClass = {}
GoodClass.__index = GoodClass -- 关键
function GoodClass.new() return setmetatable({}, GoodClass) end
function GoodClass:method() return "hello" end
local obj2 = GoodClass.new()
print(obj2:method()) -- hello
陷阱:类必须设置 __index = self,否则实例无法查找类方法。
7.2 子类构造函数未调用父类
-- lua: 错误示例
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name, breed)
local self = setmetatable({}, Dog)
-- 忘记调用 Animal.new,或未初始化 name
self.breed = breed
return self
end
local d = Dog.new("Rex", "Lab")
print(d.name) -- nil,未初始化!
print(d.breed) -- Lab
-- 正确
function Dog.new(name, breed)
local self = Animal.new(name) -- 调用父类构造
setmetatable(self, Dog) -- 重设元表为子类
self.breed = breed
return self
end
陷阱:子类构造函数必须调用父类构造函数,否则父类字段未初始化。
7.3 self 丢失
-- lua: 错误示例
local Counter = {}
Counter.__index = Counter
function Counter.new() return setmetatable({ count = 0 }, Counter) end
function Counter:inc() self.count = self.count + 1; return self.count end
local c = Counter.new()
-- 错误:用点语法调用冒号方法,丢失 self
-- print(c.inc()) -- error: attempt to index a nil value (local 'self')
-- 正确:用冒号语法
print(c:inc()) -- 1
-- 或显式传入
print(c.inc(c)) -- 2
陷阱:冒号语法 : 与点语法 . 不可混用。冒号自动传 self,点语法不传。
7.4 多重继承的方法冲突
-- lua: 多重继承菱形问题
local A = { method = function() return "A" end }
local B = setmetatable({}, { __index = A })
local C = setmetatable({}, { __index = A })
C.method = function() return "C" end -- C 重写
local D = setmetatable({}, {
__index = function(_, k)
-- 首个匹配:B 先于 C
local v = B[k]
if v ~= nil then return v end
return C[k]
end,
})
print(D.method) -- A(B 未重写,递归到 A)
-- 但如果 B 也重写,则返回 B 的版本,可能不符合预期
-- 解决方案:显式指定优先级,或用 C3 线性化
陷阱:多重继承的方法查找顺序影响行为,需明确指定优先级。
7.5 闭包封装的内存开销
-- lua: 闭包封装的内存代价
local function makeObject()
local private = 0
return {
method1 = function() private = private + 1; return private end,
method2 = function() return private end,
method3 = function() return private * 2 end,
}
end
-- 每个对象都有独立的 method1/method2/method3 闭包
-- 10000 个对象 = 30000 个闭包,内存开销大
-- 优化:用元表共享方法,仅私有状态用闭包
local Class = {}
Class.__index = Class
function Class.new()
local private = { count = 0 } -- 私有状态(可通过闭包隐藏)
return setmetatable({
_private = private, -- 仍可被外部访问,但约定不访问
}, Class)
end
function Class:method1() self._private.count = self._private.count + 1; return self._private.count end
-- 方法共享,10000 个对象仅 1 份方法
陷阱:闭包封装每个实例独立方法,大量实例时内存爆炸。权衡:真正私有 vs 内存效率。
7.6 深继承链性能
-- lua: 深继承链的性能问题
local function deepChain(depth)
local root = { method = function() return "root" end }
local current = root
for i = 1, depth do
local child = setmetatable({}, { __index = current })
child.__index = child
current = child
end
return current
end
local obj = deepChain(10)() -- 创建实例
-- 假设 obj 的元表链深 10
-- 每次访问 obj.method 需查找 10 层
-- 优化:扁平化继承,用 Mixin 替代深继承
陷阱:继承链深度 > 5 会显著拖慢热路径。建议组合优于继承。
7.7 super 调用错误
-- lua: super 调用错误
local Base = {}
Base.__index = Base
function Base.new() return setmetatable({}, Base) end
function Base:method() return "base" end
local Derived = setmetatable({}, { __index = Base })
Derived.__index = Derived
function Derived.new() return setmetatable({}, Derived) end
function Derived:method()
-- 错误:直接调用 Base.method,未传入 self
-- return Base.method() -- error: attempt to index nil (self)
-- 正确:显式传入 self
return "derived, base=" .. Base.method(self)
end
local d = Derived.new()
print(d:method()) -- derived, base=base
陷阱:调用父类方法时,必须显式传入 self:Base.method(self)。
7.8 类型检查过度依赖 getmetatable
-- lua: 类型检查的脆弱性
local Duck = {}
Duck.__index = Duck
function Duck.new() return setmetatable({}, Duck) end
function Duck:quack() return "quack" end
local function makeSound(animal)
-- 脆弱:依赖元表相等
if getmetatable(animal) ~= Duck then
error("not a Duck", 2)
end
return animal:quack()
end
-- 问题:子类实例的元表不是 Duck,会被拒绝
local Mallard = setmetatable({}, { __index = Duck })
Mallard.__index = Mallard
function Mallard.new() return setmetatable({}, Mallard) end
local m = Mallard.new()
-- makeSound(m) -- error: not a Duck,尽管 Mallard 是 Duck 的子类
-- 正确:鸭子类型,检查方法存在性
local function makeSound2(animal)
if type(animal.quack) ~= "function" then
error("cannot quack", 2)
end
return animal:quack()
end
print(makeSound2(m)) -- quack
陷阱:getmetatable 严格相等检查不识别子类。鸭子类型更灵活,推荐。
7.9 静态方法与实例方法混淆
-- lua: 静态方法与实例方法混淆
local Counter = {}
Counter.__index = Counter
Counter._count = 0 -- 静态字段(类字段)
function Counter.new()
return setmetatable({ id = Counter._count + 1 }, Counter)
end
function Counter:getId() -- 实例方法
return self.id
end
function Counter.getTotal() -- 静态方法(点语法)
Counter._count = Counter._count + 1
return Counter._count
end
local c1 = Counter.new()
print(Counter.getTotal()) -- 1
print(c1:getId()) -- 1
-- 错误:用实例调用静态方法
-- print(c1:getTotal()) -- error: self 是 c1,而非 Counter
-- 正确
print(Counter.getTotal()) -- 2
陷阱:静态方法用点语法,实例方法用冒号语法,不可混用。
7.10 多重继承 init 调用遗漏
-- lua: 多重继承时 Mixin 的 init 易遗漏
local MixinA = { initA = function(self) self.a = 1 end } }
local MixinB = { initB = function(self) self.b = 2 end } }
local function createClass(...)
local bases = { ... }
local cls = {}
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 obj.init then obj:init(...) end
return obj
end
return cls
end
local C = createClass(MixinA, MixinB)
function C:init()
-- 错误:仅调用一个 Mixin 的 init
self:initA()
-- 遗漏 self:initB(),导致 self.b 为 nil
end
local obj = C.new()
print(obj.a) -- 1
print(obj.b) -- nil
-- 正确:显式调用所有 Mixin 的 init
function C:init()
self:initA()
self:initB()
end
陷阱:多重继承时,所有 Mixin 的 init 必须显式调用,否则初始化不完整。
8. 工程实践与最佳实践
8.1 OOP 设计原则
- 组合优于继承:优先用组合(has-a)而非继承(is-a),降低耦合。
- 接口抽象:定义明确的接口(方法集),依赖抽象而非具体。
- 单一职责:每个类仅一个变化原因,职责单一。
- 开闭原则:对扩展开放,对修改关闭,通过继承或 Mixin 扩展。
- 里氏替换:子类必须能替换父类,不破坏行为契约。
- 依赖倒置:高层模块不依赖低层模块,二者依赖抽象。
8.2 类系统设计模板
-- lua: 推荐的类系统模板
local function class(base, name)
local cls = {}
if base then
setmetatable(cls, { __index = base })
cls._base = base
end
cls.__index = cls
cls.__name = name or "class"
cls.__tostring = function(self)
return "<" .. cls.__name .. " #" .. tostring(self):sub(-6) .. ">"
end
function cls.new(...)
local obj = setmetatable({}, cls)
if obj.init then obj:init(...) end
return obj
end
function cls:extend(name)
return class(self, name)
end
function cls:isInstance(obj)
local mt = getmetatable(obj)
while mt do
if mt == self then return true end
mt = getmetatable(mt) and getmetatable(mt).__index
end
return false
end
function cls:super()
return self._base
end
-- 抽象方法支持
function cls:abstract(methodName)
self[methodName] = function()
error(cls.__name .. ":" .. methodName .. " is abstract", 2)
end
end
return cls
end
-- 使用
local Animal = class(nil, "Animal")
Animal:abstract("speak")
function Animal:init(name) self.name = name end
function Animal:describe() return self.name .. ": " .. self:speak() end
local Dog = Animal:extend("Dog")
function Dog:init(name, breed)
Animal.init(self, name)
self.breed = breed
end
function Dog:speak() return "Woof" end
local d = Dog.new("Rex", "Lab")
print(d:describe()) -- Rex: Woof
print(d) -- <Dog #...>
print(Animal:isInstance(d)) -- true
8.3 私有性方案选择
| 方案 | 安全性 | 性能 | 可读性 | 适用场景 |
|---|---|---|---|---|
命名约定(_field) | 低 | 高 | 高 | 内部团队,小项目 |
| 闭包封装 | 高 | 低 | 中 | 需要真正私有,实例少 |
| 代理表 | 中 | 低 | 中 | 需要拦截访问 |
| 元表 + 命名 | 低 | 高 | 高 | 大多数场景 |
8.4 性能优化
- 共享元表:同类对象共享元表,LuaJIT 可 specialization 优化。
- 避免深继承链:继承层级 ≤ 3,超 3 用组合。
- 缓存方法引用:热路径上
local method = obj.method缓存。 - 避免代理表热路径:代理表每次访问触发元方法。
rawget内部访问:类内部用rawget绕过元方法。
8.5 测试策略
- 单元测试:为每个类编写测试,覆盖公共方法。
- 接口测试:测试类的接口(方法集)契约。
- 多态测试:测试不同子类的多态行为。
- 边界测试:测试构造、析构、错误的边界情况。
- 集成测试:测试类组合的协作行为。
8.6 调试技巧
__tostring:为类定义__tostring,改善 print 体验。__name:Lua 5.3+ 用__name在错误信息中显示类名。- 类型检查函数:
isInstance、implements辅助调试。 - 元表可视化:递归打印元表链,排查继承问题。
- 方法追踪:用装饰器或代理表记录方法调用。
9. 案例研究
9.1 案例:WoW 插件对象模型
WoW 插件用 Lua OOP 组织游戏逻辑:
-- lua: WoW 插件对象模型
local Addon = {}
Addon.__index = Addon
function Addon.new(name)
return setmetatable({
name = name,
frame = CreateFrame("Frame"),
events = {},
}, Addon)
end
function Addon:registerEvent(event, handler)
self.frame:RegisterEvent(event)
self.events[event] = handler
end
function Addon:onEvent(event, ...)
local handler = self.events[event]
if handler then handler(self, ...) end
end
function Addon:enable()
self.frame:SetScript("OnEvent", function(_, event, ...)
self:onEvent(event, ...)
end)
end
-- 使用
local MyAddon = Addon.new("MyAddon")
MyAddon:registerEvent("PLAYER_LOGIN", function(self)
print("Welcome, " .. UnitName("player"))
end)
MyAddon:enable()
9.2 案例:OpenResty Web 控制器
-- lua: OpenResty Web 控制器
local Controller = {}
Controller.__index = Controller
function Controller.new(request)
return setmetatable({
request = request,
response = { status = 200, headers = {}, body = "" },
}, Controller)
end
function Controller:json(data)
self.response.headers["Content-Type"] = "application/json"
self.response.body = require("cjson").encode(data)
return self.response
end
function Controller:html(body)
self.response.headers["Content-Type"] = "text/html"
self.response.body = body
return self.response
end
function Controller:error(status, msg)
self.response.status = status
self.response.body = msg
return self.response
end
-- 子类:用户控制器
local UserController = setmetatable({}, { __index = Controller })
UserController.__index = UserController
function UserController.new(request)
return setmetatable(Controller.new(request), UserController)
end
function UserController:profile(userId)
local user = self.request.db:getUser(userId)
if not user then
return self:error(404, "user not found")
end
return self:json(user)
end
-- 使用
local ctrl = UserController.new({ db = db })
local resp = ctrl:profile(123)
ngx.status = resp.status
for k, v in pairs(resp.headers) do ngx.header[k] = v end
ngx.say(resp.body)
9.3 案例:Roblox 游戏对象
-- luau: Roblox 游戏对象
local Character = {}
Character.__index = Character
function Character.new(model)
return setmetatable({
model = model,
health = 100,
maxHealth = 100,
}, Character)
end
function Character:takeDamage(amount)
self.health = math.max(0, self.health - amount)
if self.health == 0 then
self:onDeath()
end
end
function Character:heal(amount)
self.health = math.min(self.maxHealth, self.health + amount)
end
function Character:onDeath()
self.model:Destroy()
end
-- 子类:玩家角色
local Player = setmetatable({}, { __index = Character })
Player.__index = Player
function Player.new(model, userId)
local self = Character.new(model)
setmetatable(self, Player)
self.userId = userId
self.inventory = {}
return self
end
function Player:addItem(item)
table.insert(self.inventory, item)
end
function Player:onDeath()
-- 玩家死亡:掉落物品,重生
for _, item in ipairs(self.inventory) do
self:dropItem(item)
end
self.health = self.maxHealth
self.model:TranslateTo(Vector3.new(0, 10, 0))
end
9.4 案例:Neovim 插件对象
-- lua: Neovim 插件对象
local Plugin = {}
Plugin.__index = Plugin
function Plugin.new(name)
return setmetatable({
name = name,
commands = {},
autocmds = {},
}, Plugin)
end
function Plugin:command(name, opts, handler)
vim.api.nvim_create_user_command(name, handler, opts)
table.insert(self.commands, name)
end
function Plugin:autocmd(event, opts)
local group = vim.api.nvim_create_augroup(self.name .. "_" .. event, {})
opts.group = group
vim.api.nvim_create_autocmd(event, opts)
table.insert(self.autocmds, group)
end
-- 子类:LSP 插件
local LspPlugin = setmetatable({}, { __index = Plugin })
LspPlugin.__index = LspPlugin
function LspPlugin.new(name, serverName)
local self = Plugin.new(name)
setmetatable(self, LspPlugin)
self.serverName = serverName
return self
end
function LspPlugin:setup(opts)
self:autocmd("BufRead", {
callback = function()
vim.lsp.start({
name = self.serverName,
cmd = opts.cmd,
})
end,
})
end
-- 使用
local lsp = LspPlugin.new("my-lsp", "rust-analyzer")
lsp:setup({ cmd = { "rust-analyzer" } })
9.5 案例:Lua 游戏实体系统
-- lua: 游戏实体系统
local Entity = {}
Entity.__index = Entity
function Entity.new(id)
return setmetatable({
id = id,
components = {},
}, Entity)
end
function Entity:addComponent(name, data)
self.components[name] = data
return self
end
function Entity:getComponent(name)
return self.components[name]
end
function Entity:hasComponent(name)
return self.components[name] ~= nil
end
-- 系统:处理具有特定组件的实体
local System = {}
System.__index = System
function System.new(requiredComponents, update)
return setmetatable({
required = requiredComponents,
entities = {},
update = update,
}, System)
end
function System:matches(entity)
for _, comp in ipairs(self.required) do
if not entity:hasComponent(comp) then return false end
end
return true
end
function System:addEntity(entity)
if self:matches(entity) then
table.insert(self.entities, entity)
end
end
function System:tick(dt)
for _, entity in ipairs(self.entities) do
self.update(entity, dt)
end
end
-- 使用
local world = { entities = {}, systems = {} }
-- 创建实体
local player = Entity.new(1)
:addComponent("Position", { x = 0, y = 0 })
:addComponent("Velocity", { dx = 10, dy = 0 })
:addComponent("Health", { hp = 100 })
-- 创建系统
local movementSystem = System.new({"Position", "Velocity"}, function(e, dt)
local pos = e:getComponent("Position")
local vel = e:getComponent("Velocity")
pos.x = pos.x + vel.dx * dt
pos.y = pos.y + vel.dy * dt
end)
movementSystem:addEntity(player)
table.insert(world.systems, movementSystem)
-- 游戏循环
local function gameLoop(dt)
for _, sys in ipairs(world.systems) do
sys:tick(dt)
end
end
gameLoop(0.016)
print(player:getComponent("Position").x) -- 0.16
9.6 案例:ORM 模型层
-- lua: ORM 模型层
local Model = {}
Model.__index = Model
function Model.define(name, schema)
local cls = setmetatable({
_name = name,
_schema = schema,
}, Model)
cls.__index = cls
return cls
end
function Model.new(model, data)
local obj = setmetatable({}, model)
for field, def in pairs(model._schema) do
local value = data[field]
if value == nil then
value = def.default
end
if def.required and value == nil then
error(model._name .. "." .. field .. " is required", 2)
end
if def.validate then
def.validate(value)
end
obj[field] = value
end
return obj
end
function Model:save(db)
local data = {}
for field in pairs(self._schema) do
data[field] = self[field]
end
return db:insert(self._name, data)
end
function Model:toJson()
local parts = {}
for field in pairs(self._schema) do
parts[#parts + 1] = string.format('"%s":%s', field, tostring(self[field]))
end
return "{" .. table.concat(parts, ",") .. "}"
end
-- 使用
local User = Model.define("User", {
id = { type = "number", required = true },
name = { type = "string", required = true },
email = {
type = "string",
validate = function(v)
if v and not v:match("@") then error("invalid email", 2) end
end,
},
age = { type = "number", default = 0 },
})
local u = User.new({ id = 1, name = "Alice", email = "alice@example.com" })
print(u:toJson()) -- {"id":1,"name":"Alice","email":"alice@example.com","age":0}
9.7 案例:事件总线
-- lua: 全局事件总线(发布订阅)
local EventBus = {}
EventBus.__index = EventBus
local instance = nil
function EventBus.getInstance()
if not instance then
instance = setmetatable({
listeners = {},
}, EventBus)
end
return instance
end
function EventBus:on(event, callback, priority)
priority = priority or 0
self.listeners[event] = self.listeners[event] or {}
table.insert(self.listeners[event], { cb = callback, priority = priority })
table.sort(self.listeners[event], function(a, b) return a.priority > b.priority end)
end
function EventBus:emit(event, ...)
if not self.listeners[event] then return end
for _, entry in ipairs(self.listeners[event]) do
local ok, err = pcall(entry.cb, ...)
if not ok then
print("EventBus error in " .. event .. ": " .. tostring(err))
end
end
end
function EventBus:off(event, callback)
if not self.listeners[event] then return end
for i, entry in ipairs(self.listeners[event]) do
if entry.cb == callback then
table.remove(self.listeners[event], i)
break
end
end
end
-- 使用
local bus = EventBus.getInstance()
bus:on("user:login", function(userId)
print("user " .. userId .. " logged in")
end, 1)
bus:on("user:login", function(userId)
print("sending welcome email to " .. userId)
end, 0)
bus:emit("user:login", "Alice")
-- 输出(按优先级):
-- user Alice logged in
-- sending welcome email to Alice
9.8 案例:状态机框架
-- lua: 有限状态机框架
local StateMachine = {}
StateMachine.__index = StateMachine
function StateMachine.new(initial)
return setmetatable({
current = initial,
states = {},
transitions = {},
history = {},
}, StateMachine)
end
function StateMachine:addState(name, opts)
self.states[name] = {
onEnter = opts.onEnter,
onExit = opts.onExit,
onTick = opts.onTick,
}
end
function StateMachine:addTransition(from, to, event, guard)
self.transitions[from] = self.transitions[from] or {}
table.insert(self.transitions[from], {
to = to,
event = event,
guard = guard,
})
end
function StateMachine:fire(event)
local transitions = self.transitions[self.current]
if not transitions then return false end
for _, t in ipairs(transitions) do
if t.event == event and (not t.guard or t.guard()) then
return self:transitionTo(t.to)
end
end
return false
end
function StateMachine:transitionTo(newState)
local oldState = self.current
if self.states[oldState] and self.states[oldState].onExit then
self.states[oldState].onExit(self)
end
table.insert(self.history, oldState)
self.current = newState
if self.states[newState] and self.states[newState].onEnter then
self.states[newState].onEnter(self)
end
return true
end
function StateMachine:tick(dt)
if self.states[self.current] and self.states[self.current].onTick then
self.states[self.current].onTick(self, dt)
end
end
-- 使用:角色状态机
local sm = StateMachine.new("idle")
sm:addState("idle", {
onEnter = function() print("entering idle") end,
onTick = function(self, dt) print("idle tick") end,
})
sm:addState("moving", {
onEnter = function() print("start moving") end,
onExit = function() print("stop moving") end,
})
sm:addTransition("idle", "moving", "move")
sm:addTransition("moving", "idle", "stop")
sm:fire("move") -- start moving
sm:tick(0.016)
sm:fire("stop") -- stop moving
10. 习题与思考题
10.1 基础题
-
概念题:解释 Lua 原型继承与基于类的继承的本质差异,以及各自的优缺点。
-
代码题:实现一个
Stack类,支持push、pop、peek、size方法,以及__tostring与__len元方法。 -
代码题:实现一个
Queue类,支持enqueue、dequeue、peek方法,并继承Stack共享部分代码。 -
分析题:分析以下代码的问题,并给出修正:
local Counter = {}
function Counter.new() return setmetatable({ count = 0 }, Counter) end
function Counter:inc() self.count = self.count + 1 end
local c = Counter.new()
c.inc()
print(c.count)
- 代码题:实现一个
Timer类,支持start、stop、reset方法,以及onTick事件。
10.2 进阶题
-
设计题:设计一个完整的类系统,支持:
- 单继承与
super调用 - 抽象方法
- 属性访问器(getter/setter)
- 类型检查(
is_a、implements) tostring与eq元方法
- 单继承与
-
实现题:实现一个完整的 ORM 框架,支持:
- 模型定义
- 字段类型与验证
- 关系(一对多、多对多)
- 查询构造器
- 事务管理
-
设计题:设计一个事件驱动框架,支持:
- 多优先级监听器
- 一次性监听器
- 事件过滤
- 异步事件
- 错误隔离
-
实现题:实现一个完整的 ECS 框架,支持:
- 实体管理
- 组件注册
- 系统调度
- 查询过滤
- 性能监控
-
设计题:设计一个状态机框架,支持:
- 嵌套状态
- 历史状态
- 守卫条件
- 状态转换事件
- 状态可视化
10.3 思考题
-
开放题:Lua 无原生 OOP 语法,这是设计缺陷还是优势?对比 Java、Python、JavaScript 的 OOP 语法,讨论 Lua OOP 模式的可读性与可维护性。
-
架构题:在大型 Lua 项目(如 WoW 插件、OpenResty 服务)中,OOP 与函数式编程如何取舍?何时用 OOP,何时用纯函数?
-
演化题:Luau 的渐进式类型系统如何改变 Lua OOP 的实践?类型注解能否弥补鸭子类型的缺陷?
-
性能题:在 LuaJIT 环境下,以下两种类系统哪个性能更好?为什么?
- 基于
__index表形态的类 - 基于
__index函数形态的类
- 基于
-
批判题:SOLID 原则在 Lua 中是否完全适用?动态语言中哪些原则需调整?给出具体例子。
11. 参考文献
11.1 官方文档
-
Roberto Ierusalimschy. Programming in Lua. 4th edition. Lua.org, 2016. Chapter 16: Object-Oriented Programming.
-
Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes. Lua 5.4 Reference Manual. Lua.org, 2020. https://www.lua.org/manual/5.4/
-
The Evolution of Lua. Roberto Ierusalimschy et al. HOPL III, 2007. https://www.lua.org/doc/hopl.pdf
11.2 经典书籍
-
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
-
Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices. Prentice Hall, 2002.
-
Sandi Metz. Practical Object-Oriented Design in Ruby. O’Reilly, 2012.
-
Matthias Felleisen et al. How to Design Programs. MIT Press, 2018.
11.3 学术论文
-
The Self Papers. Urs Hölzle et al. Stanford, 1991-1995. 原型语言 Self 的奠基论文集。
-
Organizing Programs Without Classes. David Ungar. 1991.
-
Traits: Composable Units of Behaviour. Nathanael Schärli et al. ECOOP 2003.
11.4 社区资源
-
Lua Users Wiki: Object Orientation Tutorial. http://lua-users.org/wiki/ObjectOrientationTutorial
-
middleclass. Lua OOP 库,https://github.com/kikito/middleclass
-
LOOP. Lua Object-Oriented Programming,https://github.com/renatomaia/loop
-
Luau Type System. Roblox. https://luau-lang.org/typecheck
11.5 对比研究
-
ECMAScript 2024: Classes. ECMA International. https://tc39.es/ecma262/#sec-class-definitions
-
Python Data Model: Classes. Python Software Foundation. https://docs.python.org/3/tutorial/classes.html
-
Rust Traits. https://doc.rust-lang.org/book/ch10-02-traits.html
12. 延伸阅读
12.1 进阶主题
- 设计模式进阶:阅读 GoF 设计模式,在 Lua 中实现全部 23 种模式。
- 函数式 OOP:学习函数式编程与 OOP 的融合,如 React Hooks 与 Lua OOP 的对比。
- 响应式 OOP:结合代理表与观察者模式,实现 Vue/MobX 风格的响应式对象。
- 元编程 OOP:利用元方法实现 AOP(面向切面编程)、装饰器、代理。
- DSL 设计:用 OOP 与
__call构建声明式 DSL,如 HTML 生成器、查询构造器。
12.2 相关模块
lua/表与元表进阶:元表机制是 Lua OOP 的基础。lua/函数与闭包:闭包封装是实现私有性的关键。lua/环境与全局变量管理:模块即类,OOP 与模块系统紧密相关。lua/协程详解:协程与 OOP 结合,实现异步对象、生成器对象。lua/模块与包:模块化组织 OOP 代码,避免全局污染。
12.3 实践项目
- 实现完整类系统:支持继承、Mixins、抽象方法、接口检查、属性访问器、序列化。
- 构建游戏框架:ECS 架构、状态机、事件系统、资源管理。
- 设计 Web 框架:MVC 架构、路由、控制器、模型、视图。
- 开发 ORM:模型定义、关系映射、查询构造、事务、迁移。
- 构建插件系统:扩展点、依赖注入、生命周期管理、版本协商。
12.4 跨语言对比实践
- 用 Python 重新实现本文的类系统,对比元类(metaclass)与元表。
- 用 JavaScript class 语法重写本文的 OOP 示例,对比原型继承的隐式性。
- 用 Rust Trait 重写本文的 Mixin 示例,对比编译期保障与运行时灵活。
- 用 Java 接口重写本文的事件系统,对比静态类型与鸭子类型。
12.5 演进趋势
- Luau 类型系统:渐进式类型与 OOP 的融合,未来可能支持接口、抽象类的类型注解。
- 函数式 OOP 融合:Lua 社区逐渐接受函数式风格与 OOP 的混合,如不可变对象、纯方法。
- 响应式 OOP:结合代理表与依赖追踪,实现声明式响应式对象,适合 UI 开发。
- 跨语言互操作:Lua OOP 与 Rust、WebAssembly、C# 的互操作模式日趋成熟,扩展 Lua OOP 的应用场景。