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 的面向对象是"鸭子类型",不需要显式的接口声明
- 使用
rawget和rawset可以绕过元方法,直接访问/设置字段 - 类的
__tostring元方法可以自定义对象的打印格式 - 避免在构造函数中使用
self.field = value如果字段名触发了__newindex
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。