Lua中的面向对象
面向对象
先前使用了 table + function 实现了 Object 类的功能 , 但是无法将其实例化 .
配合 metatable 可以解决这一问题 .
Lua官方实现(模拟)OOP的方式
- 类定义 : 使用 metatable 描述类的属性与方法(一个new方法用于构造) .
- 实例化 : 使用 table 设置 metatable 与 __index 元方法(用new构造 , 从元表中取得属性与方法) .
- 继承 : 使用另一个 metatable 与原 metatable 设置元表(继承关系) , 从而实现继承 .
官方的做法是使用利用元表的特性从而模拟OOP , 实际上还有其他的实现(模拟)方式 .
实现继承的多种方式
- 利用元表实现 (官方做法)
- 利用复制表的方法实现
- 利用闭合函数实现
Lua对象(table)中的 self
变量 与 .
与 :
.
操作符 : 通过.
操作符可访问类(table)的成员变量 / 成员函数 .:
操作符 : 通过:
操作符可访问类(table)的成员函数 , 同时将自动将self
参数 作为第一个参数传入 .self
参数 : 如同C++中对象的this指针 , 指向对象自身 .
定义成员方法时使用.
, 类似于定义了一个”静态方法”(只是类似) , 其不会自动传入self
参数(this指针) .
利用元表实现OOP (官方做法)
Lua官方实现OOP的方式是利用元表与表的关系模拟出继承的关系
Lua官方实现(模拟)OOP的方式 (完整示例见 Code/oop/metatable.lua)
- 类定义 : 使用 metatable 描述类的属性与方法(一个new方法用于构造) .
- 实例化 : 使用 table 设置 metatable 与 __index 元方法(用new构造 , 从元表中取得属性与方法) .
- 继承 : 使用另一个 metatable 与原 metatable 设置元表(继承关系) , 从而实现继承 .
类定义
- 使用一个table(其作为metatable)定义出类的成员属性与方法(往里面塞各种不同类型的数据)
- 定义一个new方法 , 用于构造实例-返回table (其中要
设置元表关系
, 并实现__index元方法
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22print("Person--------------------")
-- 定义Person基类
Person = {
-- 成员属性
name = "" , -- 名字
age = 0, -- 年龄
num = 0 -- 人数 (实例与子类实例的总数)
}
-- 构造方法
function Person:new(name, age)
local newPerson = {} -- 实例
setmetatable(newPerson, self); -- 设置实例的元表(基类)为Person类自身
self.__index = self; -- 设置当访问实例中不存在的属性时 , 来Person类找
newPerson.name = name or "" -- 为成员属性赋值
newPerson.age = age or 0
self.num = self.num + 1 -- 静态变量人数累加
return newPerson -- 返回构造好的实例
end
-- 成员方法
function Person:speak()
print(self.name .. " " .. self.age .. ". TotalNum:" .. self.num)
end
实例化
- 调用基类(metatable)的new成员方法为变量赋值 , 该变量则为一个实例(本质还是table) .
1
2
3
4
5-- 实例化Person类
local personA = Person:new("Lee", 20)
local personB = Person:new("Van", 30)
personA:speak()
personB:speak()
继承
- 遵循类定义的方式再定义一个基类(metatable) , 同时设置两个基类(metatable)之间的继承(元表)关系 .
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
32print("Student-------------------")
-- 定义Student子类继承Person基类
Student = {
-- 子类成员属性
major = ""
}
-- 设置继承关系
setmetatable(Student, Person)
-- Student构造方法
function Student:new(name, age, major)
local newStudent = Person:new(name, age) -- 实例(调用基类构造方法)
setmetatable(newStudent, self); -- 设置实例的元表(基类)为Person类自身
self.__index = self; -- 设置当访问实例中不存在的属性时 , 来Person类找
newStudent.major = major or ""
return newStudent
end
-- Student子类特有成员方法
function Student:myMajor()
print(self.name .. " major is " .. self.major .. ". TotalNum:" .. self.num)
end
-- Student子类重写基类Person的成员方法
function Student:speak()
io.stdout:write(self.name .. " " .. self.age .. " student ; ")
end
-- 实例化Student类
local studentA = Student:new("Mike", 18, "Math")
local studentB = Student:new("Lucy", 19, "Music")
studentA:speak()
studentA:myMajor()
studentB:speak()
studentB:myMajor()
完整示例的输出
利用复制表的方法实现OOP
利用一个复制表的函数 , 实现类之间的继承关系 以及 实例化的功能 .
复制表函数实现OOP的步骤 (完整示例见 Code/oop/copyTable.lua)
- 复制table函数 : 一个用于克隆table的函数 , 将自身完整的副本返回
- 定义类 : 用metatable定义基类 , 实现一个new方法用于构造实例 .
- 实例化 : 在类的new方法中 , 利用复制table函数将类复制一份 , 将副本作为实例 .
- 继承 : 利用复制table函数 , 复制一份基类 , 将副本作为子类 .
复制table函数
一个全局函数 , 作用就是返回一个参数table的副本
1 | -- 全局函数 - 克隆表 : 返回参数表的副本 |
定义类
与用元表实现中的定义类相似 , 区别在于在new方法中 , 通过克隆自身构造实例
1 | -- Shape基类 |
实例化
直接调用类的new方法构造实例
1 | -- 实例化 Shape |
继承
同样利用克隆基类获得副本(获得基类的属性与方法) , 并在这个副本上实现子类的完整定义 .
1 | -- 子类 Square : 继承 Shape |
完整示例的输出
利用闭合函数实现OOP
利用closure的特性 , 可以将一个”类”的成员都封装在一个function内(closure可访问非局部变量 , 类似访问类内部的成员变量) .
从而实现类定义与继承 .
利用闭合函数实现OOP的步骤 (完整示例见 Code/oop/closure.lua)
- 定义类 : 用一个function封装类的所有成员 , 其中内部函数因为closure的特性 , 可以访问到”类”内部成员 .
- 实例化 : 在”类”中实现构造方法 , 将构造好的新table返回 , 该table就是实例 .
- 继承 : 在子类(function)中先获取一个基类实例 , 再定义出完整的子类 (子类的实例也是在基类实例的基础上构造而得).
定义类
用function实现类 , 需要一个self表作为实例 , 一个构造方法用于构造实例 , 其余的成员都可定义在其中(作为self表的元素) .
1 | -- 基类Vehicle |
实例化
直接调用实现类的function , 获得实例 .
1 | -- 实例化Vehicle |
继承
在子类中 , 用基类的实例先赋值self表 , 再实现子类的完整定义 .
1 | -- 子类Car |