Lua面向对象编程完全指南

全面掌握Lua面向对象编程,包括类定义、继承、多态、混入和多种设计模式的Lua实现。

Lua 面向对象基础

Lua 本身不提供面向对象语法,但通过表和元表可以构建完整的面向对象系统。核心思想是:用表表示对象,用元表的 __index 实现方法共享。

-- 最简单的"对象"
local obj = {
    name = "张三",
    age = 25
}

-- 方法
function obj:greet()
    return "你好,我是" .. self.name
end

print(obj:greet())  -- 你好,我是张三

实现一个 Class 系统

基础类定义

local Class = {}

function Class.new(base)
    local cls = {}
    cls.__index = cls

    -- 继承
    if base then
        setmetatable(cls, {__index = base})
        cls.super = base
    end

    -- 构造函数
    function cls:new(...)
        local instance = setmetatable({}, self)
        if instance.init then
            instance:init(...)
        end
        return instance
    end

    -- 类型检查
    function cls:is_a(klass)
        local mt = getmetatable(self)
        while mt do
            if mt == klass then return true end
            mt = mt.super
        end
        return false
    end

    return cls
end

使用类系统

-- 定义 Animal 类
local Animal = Class.new()

function Animal:init(name, sound)
    self.name = name
    self.sound = sound
end

function Animal:speak()
    return self.name .. " says " .. self.sound .. "!"
end

function Animal:__tostring()
    return string.format("<%s: %s>", "Animal", self.name)
end

-- 定义 Dog 类(继承 Animal)
local Dog = Class.new(Animal)

function Dog:init(name, breed)
    -- 调用父类构造
    Animal.init(self, name, "Woof")
    self.breed = breed
    self.tricks = {}
end

function Dog:learn(trick)
    table.insert(self.tricks, trick)
end

function Dog:show_tricks()
    if #self.tricks == 0 then
        return self.name .. " 还没有学会任何技能"
    end
    return self.name .. " 会: " .. table.concat(self.tricks, ", ")
end

-- 使用
local dog = Dog:new("Rex", "金毛")
print(dog:speak())          -- Rex says Woof!
dog:learn("握手")
dog:learn("翻滚")
print(dog:show_tricks())    -- Rex 会: 握手, 翻滚
print(dog:is_a(Animal))     -- true
print(dog:is_a(Dog))        -- true

方法调用语法

Lua 的冒号语法 : 是面向对象的核心便利语法:

-- 点语法:需要手动传 self
function obj.method(self, arg)
    print(self.name, arg)
end
obj.method(obj, "hello")

-- 冒号语法:自动传 self
function obj:method(arg)
    print(self.name, arg)
end
obj:method("hello")  -- 等价于 obj.method(obj, "hello")

属性的 getter/setter

通过元方法实现属性访问控制:

local function define_class(props)
    local cls = {}
    cls.__index = function(self, key)
        -- 先查找方法
        if cls[key] then return cls[key] end
        -- 查找属性 getter
        local prop = props[key]
        if prop and prop.get then
            return prop.get(self)
        end
        -- 查找存储值
        return rawget(self, "_" .. key)
    end

    cls.__newindex = function(self, key, value)
        local prop = props[key]
        if prop and prop.set then
            prop.set(self, value)
        elseif prop and prop.readonly then
            error("属性 " .. key .. " 是只读的")
        else
            rawset(self, "_" .. key, value)
        end
    end

    return cls
end

-- 定义带属性控制的类
local Person = define_class({
    name = {readonly = true},
    age = {
        get = function(self) return rawget(self, "_age") end,
        set = function(self, value)
            assert(value >= 0 and value <= 150, "年龄无效: " .. value)
            rawset(self, "_age", value)
        end
    },
    full_info = {
        get = function(self)
            return string.format("%s, %d 岁",
                rawget(self, "_name"),
                rawget(self, "_age") or 0)
        end
    }
})

function Person.new(name, age)
    local self = setmetatable({}, Person)
    rawset(self, "_name", name)
    rawset(self, "_age", age)
    return self
end

local p = Person.new("张三", 25)
print(p.name)       -- 张三
print(p.full_info)  -- 张三, 25 岁
p.age = 30
print(p.age)        -- 30
-- p.name = "李四"  -- 报错: 属性 name 是只读的
-- p.age = 200      -- 报错: 年龄无效

多继承

local function multi_class(...)
    local bases = {...}
    local cls = {}
    cls.__index = function(self, key)
        if cls[key] then return cls[key] end
        -- 在所有基类中查找
        for _, base in ipairs(bases) do
            local val = base[key]
            if val ~= nil then return val end
        end
    end

    function cls:new(...)
        local instance = setmetatable({}, self)
        -- 调用所有基类的 init
        for _, base in ipairs(bases) do
            if base.init then
                base.init(instance, ...)
            end
        end
        return instance
    end

    return cls
end

-- 示例:可序列化 + 可打印
local Serializable = {}
function Serializable:init() end
function Serializable:serialize()
    local parts = {}
    for k, v in pairs(self) do
        if type(v) ~= "function" then
            parts[#parts + 1] = k .. "=" .. tostring(v)
        end
    end
    return "{" .. table.concat(parts, ", ") .. "}"
end

local Printable = {}
function Printable:init() end
function Printable:to_string()
    return tostring(self)
end

-- 多继承类
local Widget = multi_class(Serializable, Printable)

function Widget:init(name, x, y)
    self.name = name
    self.x = x
    self.y = y
end

local w = Widget:new("按钮", 100, 200)
print(w:serialize())   -- {name=按钮, x=100, y=200}
print(w:to_string())   -- table: 0x...

Mixin 模式

Mixin 是一种轻量级的代码复用方式,将功能混入到已有类中:

local function mixin(cls, ...)
    local mixins = {...}
    for _, mix in ipairs(mixins) do
        for key, value in pairs(mix) do
            if cls[key] == nil then
                cls[key] = value
            end
        end
    end
    return cls
end

-- 定义 Mixin
local EventEmitter = {
    on = function(self, event, handler)
        self._events = self._events or {}
        self._events[event] = self._events[event] or {}
        table.insert(self._events[event], handler)
    end,

    emit = function(self, event, ...)
        self._events = self._events or {}
        local handlers = self._events[event] or {}
        for _, handler in ipairs(handlers) do
            handler(self, ...)
        end
    end,

    off = function(self, event, handler)
        self._events = self._events or {}
        local handlers = self._events[event]
        if not handlers then return end
        for i, h in ipairs(handlers) do
            if h == handler then
                table.remove(handlers, i)
                break
            end
        end
    end
}

local Loggable = {
    log = function(self, msg)
        print(string.format("[%s] %s", self.__class or "Object", msg))
    end
}

-- 使用 Mixin
local Server = Class.new()
Server.__class = "Server"
mixin(Server, EventEmitter, Loggable)

function Server:init(port)
    self.port = port
    self:log("服务器初始化在端口 " .. port)
end

function Server:start()
    self:log("服务器启动")
    self:emit("start", self.port)
end

local server = Server:new(8080)
server:on("start", function(self, port)
    self:log("监听端口: " .. port)
end)
server:start()
-- [Server] 服务器初始化在端口 8080
-- [Server] 服务器启动
-- [Server] 监听端口: 8080

单例模式

local function singleton(cls)
    local instance = nil

    return {
        get_instance = function(...)
            if not instance then
                instance = cls:new(...)
            end
            return instance
        end,

        reset = function()
            instance = nil
        end
    }
end

-- 使用
local Config = Class.new()

function Config:init()
    self.data = {}
end

function Config:get(key)
    return self.data[key]
end

function Config:set(key, value)
    self.data[key] = value
end

local ConfigSingleton = singleton(Config)

local c1 = ConfigSingleton.get_instance()
c1:set("debug", true)

local c2 = ConfigSingleton.get_instance()
print(c2:get("debug"))   -- true
print(c1 == c2)           -- true(同一个实例)

观察者模式

local Observable = {}
Observable.__index = Observable

function Observable.new()
    return setmetatable({
        _observers = {}
    }, Observable)
end

function Observable:subscribe(observer)
    table.insert(self._observers, observer)
    -- 返回取消订阅函数
    return function()
        for i, obs in ipairs(self._observers) do
            if obs == observer then
                table.remove(self._observers, i)
                break
            end
        end
    end
end

function Observable:notify(data)
    for _, observer in ipairs(self._observers) do
        observer(data)
    end
end

-- 使用
local store = Observable.new()

local unsub1 = store:subscribe(function(data)
    print("观察者1 收到:", data)
end)

local unsub2 = store:subscribe(function(data)
    print("观察者2 收到:", data)
end)

store:notify("新消息")
-- 观察者1 收到: 新消息
-- 观察者2 收到: 新消息

unsub1()  -- 取消观察者1
store:notify("又一条消息")
-- 观察者2 收到: 又一条消息

策略模式

-- 排序策略
local SortStrategies = {
    bubble = function(arr)
        local n = #arr
        for i = 1, n do
            for j = 1, n - i do
                if arr[j] > arr[j + 1] then
                    arr[j], arr[j + 1] = arr[j + 1], arr[j]
                end
            end
        end
        return arr
    end,

    insertion = function(arr)
        for i = 2, #arr do
            local key = arr[i]
            local j = i - 1
            while j > 0 and arr[j] > key do
                arr[j + 1] = arr[j]
                j = j - 1
            end
            arr[j + 1] = key
        end
        return arr
    end
}

-- 上下文
local Sorter = {}
Sorter.__index = Sorter

function Sorter.new(strategy)
    return setmetatable({
        strategy = strategy or "bubble"
    }, Sorter)
end

function Sorter:sort(arr)
    local sort_fn = SortStrategies[self.strategy]
    if not sort_fn then
        error("未知排序策略: " .. self.strategy)
    end
    return sort_fn(arr)
end

function Sorter:set_strategy(strategy)
    self.strategy = strategy
end

local sorter = Sorter.new("bubble")
print(table.concat(sorter:sort({5, 3, 1, 4, 2}), ", "))  -- 1, 2, 3, 4, 5

sorter:set_strategy("insertion")
print(table.concat(sorter:sort({9, 7, 5, 3, 1}), ", "))  -- 1, 3, 5, 7, 9

注意事项

在 Lua 中实现面向对象需要注意以下要点:

  • 冒号语法 obj:method() 自动传递 self,是最常用的方法调用方式
  • __index 元方法实现方法共享,避免每个实例都复制方法表
  • 继承链查找通过 super 字段或元表链实现
  • 多重继承性能较低,推荐使用 Mixin 替代
  • Lua 的面向对象是"鸭子类型",不需要显式的接口声明
  • 使用 rawgetrawset 可以绕过元方法,直接访问/设置字段
  • 类的 __tostring 元方法可以自定义对象的打印格式
  • 避免在构造函数中使用 self.field = value 如果字段名触发了 __newindex

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页