Lua 面向对象:基于表的实现
Lua 本身没有内置“类”的概念,但它的表极其灵活,完全能够模拟出面向对象编程中的类、对象、继承等特性。通过巧妙利用元表和 __index 机制,我们可以构建一套完整的面向对象系统。
1. 定义基础对象结构
在 Lua 中,对象本质上就是一个“键值对”的集合,也就是一个表。属性就是表中的字段,方法就是存储在表中的函数。
创建 一个名为 Person 的表,作为“类”的模板。
输入 以下代码定义基础结构:
Person = {
name = "Unknown",
age = 0
}
此时,Person 只是一个普通的表。如果要让它像一个类一样工作,我们需要定义一个“构造函数”来创建新的实例,而不是直接使用 Person 本身。
2. 实现构造函数
为了创建独立的对象实例,我们需要一个工厂函数。这个函数会创建一个新的表,并初始化它的属性。
添加 一个 new 方法到 Person 表中:
function Person.new(name, age)
local self = setmetatable({}, Person)
self.name = name or "Unknown"
self.age = age or 0
return self
end
在这段代码中:
- 调用
setmetatable({}, Person)将新创建的空表的元表设置为Person。 - 返回 初始化完成后的
self对象。
3. 理解冒号语法与 self
在 Lua 的面向对象编程中,self 代表对象本身。Lua 提供了一种语法糖来简化 self 的传递:冒号 :。
定义 一个普通方法(使用点号 .):
function Person.introduce(self)
print("My name is " .. self.name)
end
调用时必须显式传递对象:
local p = Person.new("Alice", 20)
p.introduce(p) -- 必须把 p 传进去
改用 冒号语法 : 定义方法:
function Person:introduce()
print("My name is " .. self.name)
end
调用时更加简洁,Lua 会自动将调用者作为 self 传递:
p:introduce() -- 等同于 p.introduce(p)
配置 类方法时,统一使用冒号 : 定义,这是 Lua 面向对象的最佳实践。
4. 实现继承机制
继承是面向对象的核心。在 Lua 中,继承是通过元表的 __index 元方法实现的。当我们访问一个表中不存在的字段时,Lua 会去查找该表的元表中的 __index 指向的表。
下面展示对象属性查找的流程逻辑:
操作 步骤如下:
- 创建 基类
Animal。 - 创建 子类
Dog。 - 设置
Dog的元表,使其__index指向Animal。
实现 代码如下:
-- 1. 基类 Animal
Animal = {}
function Animal:new()
local obj = { type = "Animal" }
setmetatable(obj, self)
self.__index = self
return obj
end
function Animal:speak()
print("Some generic sound")
end
-- 2. 子类 Dog
Dog = Animal:new() -- 继承:基于 Animal 创建实例,并作为 Dog 类模板
-- 3. 覆盖或扩展 Dog 的方法
function Dog:new(name)
local obj = Animal:new() -- 调用父类构造函数
setmetatable(obj, self) -- 设置元表为 Dog 自身
self.__index = self -- 确保 __index 指向自己
obj.name = name or "No Name"
obj.type = "Dog"
return obj
end
function Dog:speak()
print("Woof! My name is " .. self.name)
end
测试 继承关系:
local myDog = Dog:new("Buddy")
myDog:speak() -- 输出: Woof! My name is Buddy
print(myDog.type) -- 输出: Dog
当调用 myDog:speak() 时:
- Lua 先在
myDog表中查找speak。 - 如果没找到,通过
__index去找Dog表。 - 如果在
Dog中找到了speak(如本例),则执行它。 - 如果在
Dog中也没找到,Lua 会继续沿着Dog的元表(即Animal)向上查找。
5. 封装完整的类模板
为了在实际项目中复用,我们可以封装一个标准的 Class 函数,用于快速创建支持继承的类。
编写 通用的类生成函数:
function Class(base)
local c = {} -- 新类
if base then
c.__index = base
setmetatable(c, base) -- 设置继承
end
-- 设置元表,使得 c() 等同于 c.new()
c.__index = c
c.is = function(self, klass)
local mt = getmetatable(self)
while mt do
if mt == klass then return true end
mt = getmetatable(mt)
end
return false
end
function c:new(...)
local instance = setmetatable({}, c)
if instance.ctor then
instance:ctor(...)
end
return instance
end
return c
end
使用 该模板创建游戏中的实体类:
-- 定义 Entity 基类
Entity = Class()
function Entity:ctor(x, y)
self.x = x
self.y = y
end
function Entity:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
print("Moved to", self.x, self.y)
end
-- 定义 Player 子类
Player = Class(Entity)
function Player:ctor(name, x, y)
-- 必须显式调用父类构造函数(如果父类有逻辑处理)
-- 这里简化处理,直接初始化自身属性
self.name = name
self.x = x
self.y = y
end
function Player:move(dx, dy)
print(self.name .. " is moving...")
-- 调用父类方法
Entity.move(self, dx, dy)
end
-- 实例化与测试
local p1 = Player:new("Hero", 10, 10)
p1:move(1, 1)
-- 输出:
-- Hero is moving...
-- Moved to 11 11
通过以上步骤,你已经在 Lua 中建立了一套完整的、基于表的面向对象体系,包含了构造函数、方法定义、单冒号语法、继承以及多态(方法覆盖)。

暂无评论,快来抢沙发吧!