强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

09 - 面向对象 / Object-Oriented Programming

面向对象 / Object-Oriented Programming

Lua 没有 class 关键字,但通过 table + metatable + __index,可以实现完整的面向对象系统——继承、封装、多态,一个不少。

Lua has no class keyword, but with tables + metatables + __index, you can build a complete OOP system — inheritance, encapsulation, and polymorphism.


🟢 基础 / Basics — 用 Table 模拟类

1. 最简单的"类"

-- 用 table 模拟类
Dog = {}
Dog.__index = Dog    -- 关键:__index 指向自己

function Dog.new(name, breed)
    local self = setmetatable({}, Dog)
    self.name = name
    self.breed = breed
    return self
end

function Dog:bark()    -- 冒号语法,自动传 self
    print(self.name .. " says: Woof!")
end

function Dog:getInfo()
    return string.format("%s (%s)", self.name, self.breed)
end

-- 使用 / Usage
local buddy = Dog.new("Buddy", "Golden Retriever")
buddy:bark()                -- Buddy says: Woof!
print(buddy:getInfo())      -- Buddy (Golden Retriever)
print(buddy.name)           -- Buddy
print(type(buddy))          -- table
print(getmetatable(buddy))  -- Dog(table: 0x...)

2. 冒号语法 vs 点号语法

-- 冒号语法是点号语法的语法糖
-- Colon syntax is syntactic sugar for dot syntax

function Dog:speak(words)    -- 等价于 Dog.speak = function(self, words)
    print(self.name .. " says: " .. words)
end

-- 等价写法:
function Dog.speak(self, words)
    print(self.name .. " says: " .. words)
end

-- 调用时也一样:
buddy:speak("Woof!")      -- 等价于 Dog.speak(buddy, "Woof!")

3. 封装:私有成员

-- Lua 没有 private 关键字,但可以用闭包模拟

function newPerson(name, age)
    -- 这些是"私有"变量,外部无法直接访问
    local _name = name
    local _age = age
    
    local self = {}
    
    function self.getName() return _name end
    function self.getAge() return _age end
    function self.setAge(newAge)
        if newAge < 0 then error("Age cannot be negative") end
        _age = newAge
    end
    function self.greet()
        print("Hi, I'm " .. _name .. ", age " .. _age)
    end
    
    return self
end

local p = newPerson("Alice", 30)
p.greet()           -- Hi, I'm Alice, age 30
p.setAge(31)
-- print(p._name)   -- nil(无法直接访问)
-- p._age = -5      -- 无意义(这只是给 table 添加了一个新键)

-- 缺点:每个实例都有一份独立的方法函数(内存开销)

🟡 进阶 / Intermediate — 完整的类系统

1. 带继承的类系统

-- 基类:Animal
local Animal = {}
Animal.__index = Animal

function Animal.new(name)
    return setmetatable({name = name, energy = 100}, Animal)
end

function Animal:eat(food)
    self.energy = self.energy + 10
    print(self.name .. " eats " .. food .. " (energy: " .. self.energy .. ")")
end

function Animal:speak()
    print(self.name .. " makes a sound")
end

function Animal:__tostring()
    return string.format("[%s] %s (energy: %d)", self.class or "Animal", self.name, self.energy)
end

-- 子类:Dog 继承 Animal
local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog
Dog.class = "Dog"

function Dog.new(name, breed)
    local self = Animal.new(name)
    setmetatable(self, Dog)    -- 覆盖元表为 Dog
    self.breed = breed
    return self
end

function Dog:speak()    -- 覆盖父类方法
    print(self.name .. " says: Woof!")
end

function Dog:fetch(item)    -- 子类独有方法
    print(self.name .. " fetches " .. item)
end

-- 子类:Cat 继承 Animal
local Cat = setmetatable({}, {__index = Animal})
Cat.__index = Cat
Cat.class = "Cat"

function Cat.new(name, color)
    local self = Animal.new(name)
    setmetatable(self, Cat)
    self.color = color
    return self
end

function Cat:speak()
    print(self.name .. " says: Meow!")
end

-- 使用
local buddy = Dog.new("Buddy", "Golden")
local kitty = Cat.new("Kitty", "black")

buddy:speak()     -- Buddy says: Woof!  (多态)
kitty:speak()     -- Kitty says: Meow!  (多态)
buddy:eat("bone") -- Buddy eats bone (energy: 110)  (继承)
buddy:fetch("ball") -- Buddy fetches ball  (独有方法)
print(buddy)      -- [Dog] Buddy (energy: 110)  (__tostring)

2. isinstance 检查 / Type Checking

-- 方案一:用 class 标记
function Animal:isA(klass)
    local mt = getmetatable(self)
    while mt do
        if mt == klass then return true end
        mt = getmetatable(mt)    -- 向上查找元表链
    end
    return false
end

local buddy = Dog.new("Buddy", "Golden")
print(buddy:isA(Dog))       -- true
print(buddy:isA(Animal))    -- true
print(buddy:isA(Cat))       -- false

-- 方案二:更简单的 class 字段检查
function Animal:className() return self.class end
print(buddy:className())    -- "Dog"

3. 调用父类方法 / Calling Super Methods

local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog

function Dog:speak()
    -- 方案一:直接调用父类方法
    Animal.speak(self)    -- 先说通用声音
    print("...which is actually a Woof!")
end

-- 方案二:更灵活的方式,存储 super 引用
local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog
Dog.super = Animal    -- 保存父类引用

function Dog:speak()
    self.super.speak(self)
    print("Woof!")
end

-- 方案三:通用的 super 代理
local function createClass(parent)
    local class = {}
    class.__index = class
    class.super = parent
    if parent then
        setmetatable(class, {__index = parent})
    end
    return class
end

local Animal = createClass()
local Dog = createClass(Animal)
local Puppy = createClass(Dog)

function Animal:speak() print("...") end
function Dog:speak() 
    Dog.super.speak(self)
    print("Woof!")
end

🔴 高级 / Advanced — 高级 OOP 模式

1. Mixin 模式

-- Mixin:不通过继承,而是"混入"行为
-- Mixin: adding behavior without inheritance

local Serializable = {}
function Serializable:serialize()
    local parts = {}
    for k, v in pairs(self) do
        if type(v) ~= "function" and type(v) ~= "table" then
            parts[#parts + 1] = k .. "=" .. tostring(v)
        end
    end
    return table.concat(parts, ",")
end

local Loggable = {}
function Loggable:log(message)
    print(string.format("[%s] %s: %s", os.date(), self.name or "?", message))
end

-- 将 mixin 应用到类
local function mixin(class, ...)
    for _, m in ipairs({...}) do
        for k, v in pairs(m) do
            if class[k] == nil then    -- 不覆盖已有的方法
                class[k] = v
            end
        end
    end
    return class
end

local Player = {}
Player.__index = Player
mixin(Player, Serializable, Loggable)

function Player.new(name)
    return setmetatable({name = name, score = 0}, Player)
end

local p = Player.new("Alice")
p:log("joined the game")       -- 来自 Loggable
print(p:serialize())           -- 来自 Serializable

2. 多重继承 / Multiple Inheritance

-- Lua 不原生支持多重继承,但可以用 __index 函数实现

local function createClass(...)
    local parents = {...}
    local class = {}
    class.__index = class

    -- __index 可以是函数,按顺序查找多个父类
    setmetatable(class, {
        __index = function(_, key)
            for _, parent in ipairs(parents) do
                local value = parent[key]
                if value ~= nil then return value end
            end
            return nil
        end
    })

    function class.new(...)
        local instance = setmetatable({}, class)
        if instance.init then instance:init(...) end
        return instance
    end

    return class
end

local Flyable = {}
function Flyable:fly() print(self.name .. " flies!") end

local Swimmable = {}
function Swimmable:swim() print(self.name .. " swims!") end

local Duck = createClass(Flyable, Swimmable)
function Duck:init(name) self.name = name end
function Duck:speak() print(self.name .. " quacks!") end

local d = Duck.new("Donald")
d:speak()    -- Donald quacks!
d:fly()      -- Donald flies!
d:swim()     -- Donald swims!

3. 元类(Metatable of Metatable)

-- 用元表的元表实现"类方法"
-- 通过让类本身也是可调用的

local Class = {}
Class.__index = Class

function Class.new(name, parent)
    local cls = {name = name}
    cls.__index = cls

    if parent then
        setmetatable(cls, {__index = parent})
    end

    -- 让 cls 可以像函数一样调用来创建实例
    local class_mt = {
        __index = parent,        -- 继承查找
        __call = function(c, ...)
            local instance = setmetatable({}, c)
            if instance.init then instance:init(...) end
            return instance
        end
    }
    setmetatable(cls, class_mt)

    return cls
end

-- 使用
local Animal = Class.new("Animal")
function Animal:init(name) self.name = name end
function Animal:speak() print(self.name .. " speaks") end

local Dog = Class.new("Dog", Animal)
function Dog:speak() print(self.name .. " barks") end

-- 可以用函数调用语法创建实例
local a = Animal("Generic")
local d = Dog("Buddy")
a:speak()    -- Generic speaks
d:speak()    -- Buddy barks

小结 / Summary

层级你需要知道的 / What You Need to Know
🟢 基础table + __index 模拟类、冒号语法、self、new() 构造器
🟡 进阶继承链(setmetatable chain)、super 调用、isinstance 检查、闭包封装
🔴 高级Mixin 模式、多重继承、__call 实现构造器语法、元类

下一章:模块与包 / Modules & Packages