面向对象编程

12.1 Lua 的 OOP 思路

Lua 本身不是面向对象语言——没有 class 关键字,没有 this,没有继承语法。但借助 table + 元表,我们可以实现一套优雅的面向对象系统。

核心思想:

  • 对象 → table
  • → 原型 table
  • 方法 → table 中的函数
  • 继承__index 元方法链

12.2 self 与冒号语法

不使用冒号

1
2
3
4
5
6
7
8
9
10
11
12
local Person = {
name = "匿名",
age = 0
}

-- 方法需要显式接受 self
function Person.greet(self)
print("你好,我是" .. self.name)
end

-- 调用时需传递对象自身
Person.greet(Person) --> 你好,我是匿名

使用冒号语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local Person = {
name = "匿名",
age = 0
}

function Person:greet() -- 等价于 function Person.greet(self)
print("你好,我是" .. self.name)
end

-- 冒号调用自动传递 self
Person:greet() --> 你好,我是匿名
-- 等价于 Person.greet(Person)

local p = {name = "张三"}
setmetatable(p, {__index = Person})
p:greet() --> 你好,我是张三

记住:冒号语法 obj:method(args) 等价于 obj.method(obj, args)


12.3 实现类

最简单的”类”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 定义类(原型对象)
local Animal = {
name = "动物",
sound = "..."
}

function Animal:speak()
print(self.sound)
end

-- 创建实例(继承原型)
local mt = {__index = Animal}

local cat = {name = "猫", sound = "喵喵"}
setmetatable(cat, mt)
cat:speak() --> 喵喵

local dog = {name = "狗", sound = "汪汪"}
setmetatable(dog, mt)
dog:speak() --> 汪汪

使用构造器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- 定义一个 Animal 类
local Animal = {}

function Animal:new(name, sound)
-- self 指向类本身(Animal)
local obj = {
name = name or "动物",
sound = sound or "..."
}

-- 设置元表,使实例可以访问 Animal 的方法
setmetatable(obj, {__index = self})
return obj
end

function Animal:speak()
print(self.name .. " 发出 " .. self.sound .. " 声")
end

function Animal:describe()
print("这是一只" .. self.name)
end

-- 创建实例
local cat = Animal:new("猫", "喵喵")
cat:speak() --> 猫 发出 喵喵 声

local dog = Animal:new("狗", "汪汪")
dog:speak() --> 狗 发出 汪汪 声

-- Animal 本身也是一个对象
print(Animal:new) --> function(方法在 Animal 表里)

12.4 通用类工厂

为了避免重复写元表代码,可以封装一个类工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 类工厂
local function class(base)
local cls = {}

-- 如果有基类,从基类继承
if base then
setmetatable(cls, {__index = base})
end

-- 构造器
cls.__index = cls -- 让实例可以访问类的属性
cls.__class = cls -- 记录类本身

function cls:new(...)
local obj = {}
setmetatable(obj, {__index = self})

-- 调用初始化方法(如果存在)
if obj.init then
obj:init(...)
end

return obj
end

return cls
end

-- 使用类工厂
local Animal = class()

function Animal:init(name, sound)
self.name = name or "动物"
self.sound = sound or "..."
end

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

-- 创建实例
local cat = Animal:new("猫", "喵喵")
cat:speak() --> 猫: 喵喵

12.5 继承

单继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
local Animal = class()

function Animal:init(name, sound)
self.name = name or "动物"
self.sound = sound or "..."
end

function Animal:speak()
print(self.name .. " 发出 " .. self.sound)
end

-- 继承 Animal
local Mammal = class(Animal)

function Mammal:init(name, sound, fur_color)
-- 调用父类初始化
Animal.init(self, name, sound)
self.fur_color = fur_color or "未知"
end

function Mammal:describe()
print("一只" .. self.fur_color .. "毛的" .. self.name)
end

-- 更深一层继承
local Cat = class(Mammal)

function Cat:init(name, fur_color)
Mammal.init(self, name or "猫", "喵喵", fur_color)
end

-- 重写方法
function Cat:speak()
print(self.name .. " 温柔地 " .. self.sound)
end

-- 测试
local kitty = Cat:new("小橘", "橘色")
kitty:speak() --> 小橘 温柔地 喵喵
kitty:describe() --> 一只橘色毛的小橘

-- 方法查找链:kitty → Cat → Mammal → Animal
print(kitty.__class) --> Cat(类)

方法继承链验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local function instance_of(obj, cls)
local meta = getmetatable(obj)
if not meta then return false end

local current = meta.__index
while current do
if current == cls then return true end
local parent_meta = getmetatable(current)
current = parent_meta and parent_meta.__index or nil
end

return false
end

print(instance_of(kitty, Cat)) --> true
print(instance_of(kitty, Mammal)) --> true
print(instance_of(kitty, Animal)) --> true
print(instance_of(kitty, class())) --> false

12.6 多继承

通过自定义 __index 在多个父类中搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
local function multi_class(...)
local parents = {...}
local cls = {}
cls.__class = cls

cls.__index = function(obj, key)
-- 先在自己的静态属性中查找
if cls[key] ~= nil then
return cls[key]
end

-- 在所有父类中查找
for _, parent in ipairs(parents) do
local value = parent[key]
if value ~= nil then
-- 如果是函数,绑定到当前对象
if type(value) == "function" then
return function(...)
return value(obj, ...)
end
end
return value
end
end
return nil
end

function cls:new(...)
local obj = {}
setmetatable(obj, {__index = self.__index})
if obj.init then
obj:init(...)
end
return obj
end

return cls
end

-- 定义两个基类
local Flyable = class()
function Flyable:fly()
print(self.name .. " 在飞")
end

local Swimmable = class()
function Swimmable:swim()
print(self.name .. " 在游泳")
end

-- 多继承
local Duck = multi_class(Flyable, Swimmable)
function Duck:init(name)
self.name = name or "鸭子"
end

local d = Duck:new("唐老鸭")
d:fly() --> 唐老鸭 在飞
d:swim() --> 唐老鸭 在游泳

12.7 私有成员模拟

使用闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
local function create_bank_account(initial_balance)
-- 私有变量(通过闭包隐藏)
local _balance = initial_balance or 0

-- 公开接口
return {
deposit = function(self, amount)
assert(amount > 0, "存款金额必须大于 0")
_balance = _balance + amount
return true
end,

withdraw = function(self, amount)
assert(amount > 0, "取款金额必须大于 0")
if _balance >= amount then
_balance = _balance - amount
return true
end
return false
end,

get_balance = function(self)
return _balance
end,

transfer = function(self, target, amount)
if self:withdraw(amount) then
target:deposit(amount)
return true
end
return false
end
}
end

local acc1 = create_bank_account(1000)
local acc2 = create_bank_account(500)

print(acc1.get_balance()) --> 1000
print(acc1._balance) --> nil(无法直接访问)
acc1:deposit(300)
print(acc1.get_balance()) --> 1300
acc1:transfer(acc2, 200)
print(acc1.get_balance()) --> 1100
print(acc2.get_balance()) --> 700

使用命名约定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local Person = class()

function Person:init(name, age, id)
self.name = name
self.age = age
self._id = id -- 以下划线开头表示"私有"
self.__secret = "秘密" -- 双下划线表示"非常私有"
end

function Person:get_id()
return self._id
end

local p = Person:new("张三", 25, "ID001")
print(p.name) --> 张三(可访问)
print(p._id) --> ID001(技术上仍可访问)
print(p.get_id()) --> ID001(通过方法访问)

12.8 静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local User = class()

-- 静态属性
User.count = 0

-- 实例方法
function User:init(name)
self.name = name
User.count = User.count + 1 -- 访问静态属性
self.id = User.count
end

-- 静态方法
function User.total()
return User.count
end

local u1 = User:new("张三")
local u2 = User:new("李四")
local u3 = User:new("王五")

print(User.total()) --> 3
print(u1.id) --> 1
print(u3.id) --> 3

12.9 完整的 OOP 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
-- class.lua
local function class(base)
local cls = {}
cls.__class = cls

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

cls.__index = cls

function cls:new(...)
local obj = {}
setmetatable(obj, {__index = self})
if obj.init then
obj:init(...)
end
return obj
end

return cls
end

-- 使用示例:图形类体系
local Shape = class()

function Shape:init(name)
self.name = name or "形状"
end

function Shape:area()
return 0
end

function Shape:describe()
return string.format("%s 的面积是 %.2f", self.name, self:area())
end

-- 矩形
local Rectangle = class(Shape)

function Rectangle:init(width, height)
Shape.init(self, "矩形")
self.width = width or 0
self.height = height or 0
end

function Rectangle:area()
return self.width * self.height
end

-- 圆形
local Circle = class(Shape)

function Circle:init(radius)
Shape.init(self, "圆形")
self.radius = radius or 0
end

function Circle:area()
return math.pi * self.radius ^ 2
end

-- 使用
local rect = Rectangle:new(10, 5)
print(rect:describe()) --> 矩形的面积是 50.00

local circle = Circle:new(7)
print(circle:describe()) --> 圆形的面积是 153.94

-- 多态
local shapes = {rect, circle}
for _, s in ipairs(shapes) do
print(s:describe())
end

12.10 本章小结

概念 Lua 实现
对象 table
原型 table(包含方法和默认属性)
方法 table 中的函数
self 冒号语法自动传递
继承 __index 元表链
多继承 自定义 __index 函数
封装 闭包隐藏变量
多态 方法重写

练习题

  1. 用 class 工厂实现一个员工管理系统:Employee 继承 Person,增加 salarywork() 方法
  2. 实现一个 interface(cls, methods) 函数,检查 cls 是否实现了所有指定方法
  3. 实现一个单例类(Singleton),确保 new() 始终返回同一个实例
  4. 给 class 工厂增加 extends(cls) 方法,替代直接传参方式
  5. 实现一个 Mixin 系统,允许动态混入方法到类中
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Lua基础教程 系列
第 12/15 篇
分享: