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 实现构造器语法、元类 |