前置知识: LuaLuaLua

面向对象编程

46 minIntermediate2026/7/21

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:setmetatablegetmetatablerawgetrawsetself 隐式参数。
  • 描述类(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 等原型语言的异同:对象克隆机制、原型链查找、__index vs __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 范式演化

面向对象编程历经四个主要阶段:

  1. Simula 阶段(1960s):Simula 67 引入类、对象、继承概念,用于仿真建模。
  2. Smalltalk 阶段(1970s-1980s):Smalltalk 确立”一切皆对象”哲学,消息传递机制,纯 OOP 语言。
  3. C++/Java 阶段(1980s-1990s):静态类型 OOP 成为主流,类、继承、多态、虚函数、接口标准化。
  4. 动态 OOP 与原型语言(1990s-):JavaScript、Lua、Python、Ruby 推动动态 OOP,原型继承、鸭子类型、元编程兴起。

Lua OOP 属于第四阶段,基于原型继承(prototype-based inheritance),与 JavaScript、Self 同源。Lua 选择原型继承的设计动机:

  1. 简洁性:无需引入新的语法结构(class 关键字),元表机制已足够表达继承。
  2. 一致性:表是唯一的复合数据结构,OOP 只是元表的用法之一,不破坏语言一致性。
  3. 可嵌入性:Lua 作为嵌入式脚本,不应强加 OOP 范式给宿主,提供机制而非策略。
  4. 教学简洁:原型继承比类继承更直观(对象克隆对象),降低学习门槛。

2.2 Lua OOP 的诞生动机

Lua 3.1(1998)引入 __index 元方法,为 OOP 提供基础。其设计动机:

  1. 代码复用:大型 Lua 项目需要继承机制减少重复代码,__index 链实现方法复用。
  2. 抽象数据类型:游戏对象(WoW)、UI 组件(Love2D)、配置对象需要 OOP 抽象。
  3. C API 桥接:C 扩展通过元表暴露”对象”给 Lua,OOP 模式使 Lua 端使用更自然。
  4. 设计模式:Lua 社区需要经典设计模式(观察者、单例等)解决工程问题,OOP 是基础。
  5. 跨语言互操作:与 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 设计遵循以下原则:

  1. 机制而非策略:语言提供元表机制,具体 OOP 模式由库与开发者决定。
  2. 鸭子类型:无接口声明,运行时检查方法存在性,灵活但需测试保障。
  3. 原型优先:对象直接克隆对象,类只是惯例(元表模式),非语言强制。
  4. 可选 OOP:Lua 代码可不使用 OOP,函数式、过程式均可,不强制范式。
  5. 零开销抽象:不用 OOP 时无性能损失,用 OOP 时元表查找开销可控。
  6. 可扩展:开发者可构建自己的类系统,如 middleclass、LOOP、LuaOOP 等库。

3. 形式化定义

3.1 对象的代数模型

Lua 对象 OO 是一个表,其字段包括属性(property)与方法(method)。方法通过元表 MM 提供:

O=P,MO = \langle P, M \rangle

其中:

  • P:NameValueP : \text{Name} \to \text{Value} 是属性映射,存储于对象本身。
  • M:NameFunctionM : \text{Name} \to \text{Function} 是方法映射,存储于元表。

对象 OO 的元表为 MM:mt(O)=M\text{mt}(O) = M,M.__index=MM.\text{\_\_index} = M(类自身的 __index 指向自身)。

3.2 类的形式化

CC 是一个特殊的表,既是元表又是方法容器:

C=M,newC = \langle M, \text{new} \rangle

其中:

  • M:NameFunctionM : \text{Name} \to \text{Function} 是方法集合。
  • new:ArgsO\text{new} : \text{Args} \to O 是构造函数,创建对象 OO 并设置 mt(O)=C\text{mt}(O) = C

构造函数语义:

new(C,args)=let O={};mt(O)C;init(O,args);O\text{new}(C, \text{args}) = \text{let } O = \{\}; \text{mt}(O) \leftarrow C; \text{init}(O, \text{args}); O

3.3 原型继承的形式化

原型继承(prototype-based inheritance):对象 OO 的方法查找通过元表链进行:

lookup(O,m)={O[m]if mdom(O)lookup(mt(O),m)otherwise\text{lookup}(O, m) = \begin{cases} O[m] & \text{if } m \in \text{dom}(O) \\ \text{lookup}(\text{mt}(O), m) & \text{otherwise} \end{cases}

mt(O)=C\text{mt}(O) = C(类),C.__index=CC.\text{\_\_index} = C,则 lookup(O,m)=C[m]\text{lookup}(O, m) = C[m]

继承:子类 SS 继承父类 BB,通过设置 mt(S)={__index=B}\text{mt}(S) = \{ \text{\_\_index} = B \}:

inherits(S,B)=mt(S){__index=B}\text{inherits}(S, B) = \text{mt}(S) \leftarrow \{ \text{\_\_index} = B \}

方法查找沿继承链递归:

lookup(S,m)={S[m]if mdom(S)lookup(B,m)otherwise\text{lookup}(S, m) = \begin{cases} S[m] & \text{if } m \in \text{dom}(S) \\ \text{lookup}(B, m) & \text{otherwise} \end{cases}

3.4 self 参数的形式化

冒号语法 obj:method(args) 等价于 obj.method(obj, args),self 自动绑定:

obj:method(args)=obj.method(obj,args)\text{obj:method}(\text{args}) = \text{obj.method}(\text{obj}, \text{args})

定义方法时,function C:method(args) ... end 等价于 function C.method(self, args) ... end:

C:method(args)C.method(self,args)\text{C:method}(\text{args}) \equiv \text{C.method}(\text{self}, \text{args})

3.5 多态的形式化

多态(polymorphism):不同对象对同一消息(方法名)作出不同响应。

设对象 O1O_1O2O_2 均响应方法 mm,但实现不同:

O1.mO2.mbutlookup(O1,m)lookup(O2,m)O_1.m \neq O_2.m \quad \text{but} \quad \text{lookup}(O_1, m) \neq \bot \land \text{lookup}(O_2, m) \neq \bot

调用 call(O,m,args)\text{call}(O, m, \text{args}) 根据对象类型分派:

call(O,m,args)=lookup(O,m)(O,args)\text{call}(O, m, \text{args}) = \text{lookup}(O, m)(O, \text{args})

Lua 多态基于鸭子类型:无需声明接口,运行时检查方法存在性。

3.6 封装的形式化

封装(encapsulation):隐藏对象内部状态,仅通过方法访问。

Lua 无原生 private 关键字,通过以下方式模拟:

  1. 闭包封装:对象工厂函数返回闭包,内部变量不可外部访问:
create(init)=let state=init;return {methods using state}\text{create}(\text{init}) = \text{let } \text{state} = \text{init}; \text{return } \{ \text{methods using state} \}
  1. 命名约定:以 _ 前缀标记”私有”字段,运行时不强制,仅约定。

  2. 代理表隔离:外层代理表拦截所有访问,内层数据表存储真实状态。

3.7 多重继承的形式化

多重继承:类 SS 继承多个基类 B1,B2,,BnB_1, B_2, \ldots, B_n:

multiInherit(S,[B1,,Bn])=mt(S).__index=λk.first Bi[k] that is not nil\text{multiInherit}(S, [B_1, \ldots, B_n]) = \text{mt}(S).\text{\_\_index} = \lambda k. \text{first } B_i[k] \text{ that is not nil}

方法查找:

lookup(S,m)={S[m]if mdom(S)Bi[m]for first i s.t. mdom(Bi)otherwise\text{lookup}(S, m) = \begin{cases} S[m] & \text{if } m \in \text{dom}(S) \\ B_i[m] & \text{for first } i \text{ s.t. } m \in \text{dom}(B_i) \\ \bot & \text{otherwise} \end{cases}

注意:Lua 采用”首个匹配”策略,与 Python C3 线性化不同,可能导致菱形问题。

3.8 抽象类的形式化

抽象类(abstract class):包含抽象方法(未实现)的类,子类必须实现:

abstract(C,A)=C[m]=λerror("abstractmethod"..m.."notimplemented")mA\text{abstract}(C, A) = C[m] = \lambda \ldots \text{error}("abstract method " .. m .. " not implemented") \quad \forall m \in A

子类 SS 继承 CC 后必须重写 AA 中所有方法:

concrete(S)    mA:S[m]C[m]\text{concrete}(S) \iff \forall m \in A: S[m] \neq C[m]

4. 理论推导与证明

4.1 原型继承与类继承的等价性

命题 1:原型继承与类继承在表达能力上等价。

证明:

(1) 类继承可由原型继承模拟:

设类继承中类 CC 继承 BB,实例 OOCC 的实例。在原型继承中,设 OO 的原型为 CC,CC 的原型为 BB:

  • OO 查找方法 mm:先查 OO,再查 CC,再查 BB
  • 等价于类继承中 OO 查找 mm:先查实例字段,再查类 CC,再查父类 BB

(2) 原型继承可由类继承模拟:

设原型继承中 O1O_1 克隆自 O2O_2。在类继承中,设 O2O_2 是”类”,O1O_1 是其实例:

  • O1O_1 查找方法 mm:先查 O1O_1,再查 O2O_2(作为类)。
  • 等价于原型继承中 O1O_1 查找 mm:先查 O1O_1,再查原型 O2O_2

故两者表达能力等价。但语义上有差异:

  • 类继承:类是”模板”,实例是”产品”,二者角色明确。
  • 原型继承:对象直接克隆对象,无类与实例的严格区分。

证毕。

4.2 方法查找复杂度

命题 2:单继承方法查找的最坏时间复杂度为 O(D)O(D),其中 DD 为继承深度。

证明:

设继承链 C1C2CDC_1 \to C_2 \to \ldots \to C_D(C1C_1 是子类,CDC_D 是根类),对象 OO 的元表为 C1C_1

查找 O.mO.m:

  1. O[m]O[m]:若存在,返回;否则进入 C1C_1
  2. C1[m]C_1[m]:若存在,返回;否则进入 C2C_2
  3. CD[m]C_D[m]:若存在,返回;否则返回 nil。

最坏情况下,mm 不存在于任何 CiC_i,需遍历整条链,共 DD 次查找。每次查找 O(1)O(1)(哈希表),故总复杂度 O(D)O(D)

推论:深继承链(D>5D > 5)会显著拖慢热路径,应避免。

证毕。

4.3 多重继承的菱形问题

命题 3:Lua 多重继承采用”首个匹配”策略,可能产生菱形问题。

证明:

设类层次:

      A
     / \
    B   C
     \ /
      D

AA 定义方法 mm,BBCC 均继承 AA 但未重写 mm,DD 多重继承 BBCC

Lua 查找 D.mD.m:

  1. D[m]D[m]:不存在。
  2. B[m]B[m]:不存在(BB 未重写)。
  3. C[m]C[m]:不存在(CC 未重写)。
  4. 返回 nil。

问题:AAmm 未被查到,因为 Lua 的”首个匹配”仅查 BBCC 的直接字段,不递归到 AA

解决方案:多重继承的 __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,递归到 AA,正确返回 A.mA.m

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)。

代价:每个实例的 depositgetBalance 方法是独立闭包,不共享,内存开销大于元表共享方法。

证毕。

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,无需声明接口。

劣势:运行时才发现错误,重构时(如重命名 eatconsume)无编译期提示。

弥补:通过单元测试覆盖,或使用 Luau 渐进式类型系统添加类型注解。

证毕。

4.6 构造函数调用顺序

命题 6:子类构造函数应显式调用父类构造函数,顺序为”父类先,子类后”。

证明:

设类层次 BSB \to SSS 继承 BB),对象 OOSS 的实例。

正确构造顺序:

function S.new(...)
    local self = setmetatable({}, S)
    B.init(self, ...)  -- 先调用父类 init,初始化父类字段
    S.init(self, ...)  -- 再调用子类 init,初始化子类字段
    return self
end

顺序分析:

  1. B.init 设置 OO 的父类字段(如 nameage)。
  2. S.init 设置 OO 的子类字段(如 breedtricks),可能依赖父类字段。

若顺序颠倒(子类先):

  1. 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 OOPJavaScript OOP
继承机制元表 __index[[Prototype]]
类语法无(基于元表惯例)class 关键字(ES6+)
this/selfself(冒号语法自动传入)this(调用上下文绑定)
私有性闭包或命名约定# 私有字段(ES2022+)
多重继承支持(__index 函数形态)不支持(mixin 模式)
类型检查鸭子类型鸭子类型 + TypeScript
静态方法类表字段static 关键字
构造函数自定义 new 函数constructor 方法
性能元表查找开销V8 隐藏类优化

Lua 与 JavaScript 同为原型语言,但 JavaScript 在 ES6 后提供 class 语法糖,而 Lua 始终基于元表惯例。

6.2 与 Python OOP 对比

维度Lua OOPPython OOP
类语法无(元表惯例)class 关键字
继承机制元表链类 MRO(C3 线性化)
多重继承支持(首个匹配)支持(C3 MRO)
私有性闭包或约定_/__ 命名约定
接口鸭子类型ABC + isinstance
静态方法类表字段@staticmethod
类方法类表字段@classmethod
属性访问器__index/__newindex@property
元编程元方法__getattr__/__setattr__

Python 提供更丰富的 OOP 语法糖,但 Lua 的元表机制更灵活、可定制。

6.3 与 Java OOP 对比

维度Lua OOPJava OOP
类型系统动态静态
类语法class 关键字
继承单继承 + Mixin单继承 + 接口
多态鸭子类型接口 + 虚方法
访问控制private/protected/public
抽象类模拟(错误抛出)abstract 关键字
接口模拟(运行时检查)interface 关键字
重写约定@Override
性能元表查找虚方法分派(JIT 优化)
工具支持强(IDE 重构、导航)

Java 提供完整的 OOP 类型系统与工具链;Lua 以灵活性换取简洁,适合快速原型。

6.4 与 C++ OOP 对比

维度Lua OOPC++ OOP
类型系统动态静态
内存管理GC手动 / RAII
继承单/Multiple单/多/虚继承
多态鸭子类型虚函数(vtable)
模板template
运算符重载元方法operator+ 等
性能元表查找编译期绑定,零开销
RTTItypeid/dynamic_cast

C++ 追求性能与静态安全;Lua 追求灵活与简洁。

6.5 与 Rust Trait 对比

维度Lua OOPRust Trait
类型系统动态静态
继承原型继承无继承,Trait 组合
多态鸭子类型Trait object / 泛型
抽象命名约定Trait
默认实现MixinTrait 默认方法
性能元表查找静态分派 / 动态分派
安全编译期保障

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 设计原则

  1. 组合优于继承:优先用组合(has-a)而非继承(is-a),降低耦合。
  2. 接口抽象:定义明确的接口(方法集),依赖抽象而非具体。
  3. 单一职责:每个类仅一个变化原因,职责单一。
  4. 开闭原则:对扩展开放,对修改关闭,通过继承或 Mixin 扩展。
  5. 里氏替换:子类必须能替换父类,不破坏行为契约。
  6. 依赖倒置:高层模块不依赖低层模块,二者依赖抽象。

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 性能优化

  1. 共享元表:同类对象共享元表,LuaJIT 可 specialization 优化。
  2. 避免深继承链:继承层级 ≤ 3,超 3 用组合。
  3. 缓存方法引用:热路径上 local method = obj.method 缓存。
  4. 避免代理表热路径:代理表每次访问触发元方法。
  5. rawget 内部访问:类内部用 rawget 绕过元方法。

8.5 测试策略

  1. 单元测试:为每个类编写测试,覆盖公共方法。
  2. 接口测试:测试类的接口(方法集)契约。
  3. 多态测试:测试不同子类的多态行为。
  4. 边界测试:测试构造、析构、错误的边界情况。
  5. 集成测试:测试类组合的协作行为。

8.6 调试技巧

  1. __tostring:为类定义 __tostring,改善 print 体验。
  2. __name:Lua 5.3+ 用 __name 在错误信息中显示类名。
  3. 类型检查函数:isInstanceimplements 辅助调试。
  4. 元表可视化:递归打印元表链,排查继承问题。
  5. 方法追踪:用装饰器或代理表记录方法调用。

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 基础题

  1. 概念题:解释 Lua 原型继承与基于类的继承的本质差异,以及各自的优缺点。

  2. 代码题:实现一个 Stack 类,支持 pushpoppeeksize 方法,以及 __tostring__len 元方法。

  3. 代码题:实现一个 Queue 类,支持 enqueuedequeuepeek 方法,并继承 Stack 共享部分代码。

  4. 分析题:分析以下代码的问题,并给出修正:

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)
  1. 代码题:实现一个 Timer 类,支持 startstopreset 方法,以及 onTick 事件。

10.2 进阶题

  1. 设计题:设计一个完整的类系统,支持:

    • 单继承与 super 调用
    • 抽象方法
    • 属性访问器(getter/setter)
    • 类型检查(is_aimplements)
    • tostringeq 元方法
  2. 实现题:实现一个完整的 ORM 框架,支持:

    • 模型定义
    • 字段类型与验证
    • 关系(一对多、多对多)
    • 查询构造器
    • 事务管理
  3. 设计题:设计一个事件驱动框架,支持:

    • 多优先级监听器
    • 一次性监听器
    • 事件过滤
    • 异步事件
    • 错误隔离
  4. 实现题:实现一个完整的 ECS 框架,支持:

    • 实体管理
    • 组件注册
    • 系统调度
    • 查询过滤
    • 性能监控
  5. 设计题:设计一个状态机框架,支持:

    • 嵌套状态
    • 历史状态
    • 守卫条件
    • 状态转换事件
    • 状态可视化

10.3 思考题

  1. 开放题:Lua 无原生 OOP 语法,这是设计缺陷还是优势?对比 Java、Python、JavaScript 的 OOP 语法,讨论 Lua OOP 模式的可读性与可维护性。

  2. 架构题:在大型 Lua 项目(如 WoW 插件、OpenResty 服务)中,OOP 与函数式编程如何取舍?何时用 OOP,何时用纯函数?

  3. 演化题:Luau 的渐进式类型系统如何改变 Lua OOP 的实践?类型注解能否弥补鸭子类型的缺陷?

  4. 性能题:在 LuaJIT 环境下,以下两种类系统哪个性能更好?为什么?

    • 基于 __index 表形态的类
    • 基于 __index 函数形态的类
  5. 批判题:SOLID 原则在 Lua 中是否完全适用?动态语言中哪些原则需调整?给出具体例子。

11. 参考文献

11.1 官方文档

  1. Roberto Ierusalimschy. Programming in Lua. 4th edition. Lua.org, 2016. Chapter 16: Object-Oriented Programming.

  2. Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes. Lua 5.4 Reference Manual. Lua.org, 2020. https://www.lua.org/manual/5.4/

  3. The Evolution of Lua. Roberto Ierusalimschy et al. HOPL III, 2007. https://www.lua.org/doc/hopl.pdf

11.2 经典书籍

  1. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.

  2. Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices. Prentice Hall, 2002.

  3. Sandi Metz. Practical Object-Oriented Design in Ruby. O’Reilly, 2012.

  4. Matthias Felleisen et al. How to Design Programs. MIT Press, 2018.

11.3 学术论文

  1. The Self Papers. Urs Hölzle et al. Stanford, 1991-1995. 原型语言 Self 的奠基论文集。

  2. Organizing Programs Without Classes. David Ungar. 1991.

  3. Traits: Composable Units of Behaviour. Nathanael Schärli et al. ECOOP 2003.

11.4 社区资源

  1. Lua Users Wiki: Object Orientation Tutorial. http://lua-users.org/wiki/ObjectOrientationTutorial

  2. middleclass. Lua OOP 库,https://github.com/kikito/middleclass

  3. LOOP. Lua Object-Oriented Programming,https://github.com/renatomaia/loop

  4. Luau Type System. Roblox. https://luau-lang.org/typecheck

11.5 对比研究

  1. ECMAScript 2024: Classes. ECMA International. https://tc39.es/ecma262/#sec-class-definitions

  2. Python Data Model: Classes. Python Software Foundation. https://docs.python.org/3/tutorial/classes.html

  3. 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 实践项目

  1. 实现完整类系统:支持继承、Mixins、抽象方法、接口检查、属性访问器、序列化。
  2. 构建游戏框架:ECS 架构、状态机、事件系统、资源管理。
  3. 设计 Web 框架:MVC 架构、路由、控制器、模型、视图。
  4. 开发 ORM:模型定义、关系映射、查询构造、事务、迁移。
  5. 构建插件系统:扩展点、依赖注入、生命周期管理、版本协商。

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 的应用场景。
返回入门指南