文章目录

Lua 面向对象:基于表的实现

发布于 2026-04-16 21:28:19 · 浏览 14 次 · 评论 0 条

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 指向的表。

下面展示对象属性查找的流程逻辑:

graph LR A[SubClass Object] -->|Key Not Found| B[Metatable] B -->|__index| C[Base Class] C -->|Key Found| D[Return Value]

操作 步骤如下:

  1. 创建 基类 Animal
  2. 创建 子类 Dog
  3. 设置 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() 时:

  1. Lua 先在 myDog 表中查找 speak
  2. 如果没找到,通过 __index 去找 Dog 表。
  3. 如果在 Dog 中找到了 speak(如本例),则执行它。
  4. 如果在 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 中建立了一套完整的、基于表的面向对象体系,包含了构造函数、方法定义、单冒号语法、继承以及多态(方法覆盖)。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文