建议提前学习https://www.runoob.com/lua/lua-metatables.html
面向对象特征
- 1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 2) 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 3) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 4)抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
类
lua并没有严格的面向对象的语法,我们只是使用表的特性来模拟类。
lua是duck风格,不存在访问性的问题。所以我们这里考虑如下四种概念:
- 静态变量:属于该类的变量
- 静态函数:属于该类的函数
- 成员变量:属于该类对象的变量
- 成员函数:属于该类对象的函数
一个类是一个元表,表中存储静态变量、静态函数、以及对象的成员函数。
一个对象是一张表,表中存储了成员变量。
如下表:
属于 | 代码位置 | 写法 | |
静态变量 | 类 | 元表 | . |
静态函数 | 类 | 元表 | . |
成员变量 | 对象 | 表 | . |
成员函数 | 对象 | 元表 | : |
示例如下:
A = { -- 类名为 AstaticVar = 0 -- 静态变量
}
A.__index = A -- 当做格式即可function A.StaticFunc() -- 静态函数print("staticVar is " .. A.staticVar) -- 可以访问静态变量
endfunction A:New(number) -- 定义构造函数,使用其它名称也是可以的-- self 类似 this 指针local obj = {}setmetatable(obj, self) -- 将对象 obj 的元表设置为类 Aobj.normalVar = number or 0 -- 使用 or 来制定默认值return obj
endfunction A:NormalMethod() -- 成员函数print("normalVar is " .. self.normalVar) -- 可以访问成员变量
endlocal a1 = A:New(1) -- 调用构造函数来创建对象
local a2 = A:New(2)
a1.normalVar = 10 -- 修改成员变量
a1:NormalMethod() -- 调用成员函数
a2:NormalMethod()A:StaticFunc() -- 通过类名调用静态函数
a1:StaticFunc() -- 通过对象调用静态函数
输出如下:
normalVar is 10
normalVar is 2
staticVar is 0
staticVar is 0
最后补充一个讨论,调用时这两种写法等价:
a1:NormalMethod() -- 调用普通方法
a1.NormalMethod(a1) -- 等价于上述写法
但是显然第二种写法危险得多,因为可能第二个a1会拼写错误。
类继承
虽然lua不鼓励大家写继承,但是还是能写的。
对于单继承的情况,将子类的元表设置成基类即可。例如:
setmetatable(B, A) -- B 继承了 A
示例如下:
A = { -- 基类名为 AstaticVar = 0 -- 静态变量
}
A.__index = A -- 当做格式即可function A.StaticFunc() -- 静态函数print("staticVar is " .. A.staticVar) -- 可以访问静态变量
endfunction A:New(number) -- 定义构造函数,使用其它名称也是可以的-- self 类似 this 指针local obj = {}setmetatable(obj, self) -- 将对象 obj 的元表设置为类 Aobj.normalVar = number or 0 -- 使用 or 来制定默认值return obj
endfunction A:NormalMethod() -- 成员函数print("normalVar is " .. self.normalVar) -- 可以访问成员变量
endB = {} -- 继承类名为 B
B.__index = B -- 当做格式即可
setmetatable(B, A) -- B 继承了 Afunction B:New(number) -- 定义构造函数,使用其它名称也是可以的-- self 类似 this 指针local obj = A:New(number) -- 使用基类的构造函数setmetatable(obj, self) -- 将对象 obj 的元表设置为类 Bobj.normalVar2 = number + 1 or 0 -- 使用 or 来制定默认值return obj
endfunction B:NormalMethod2() -- 成员函数print("normalVar2 is " .. self.normalVar2) -- 可以访问成员变量
endlocal b = B:New(1) -- 调用构造函数来创建对象
b.normalVar = 10 -- 修改成员变量
b:NormalMethod() -- 调用成员函数
b.normalVar2 = 11
b:NormalMethod2()
b.StaticFunc() -- 可以通过派生类对象访问基类的静态函数
输出如下:
normalVar is 10
normalVar2 is 11
staticVar is 0
覆盖
另外,由于__index寻址的问题,子类的同名函数会覆盖基类的函数。例如:
A = { -- 基类名为 AstaticVar = 0 -- 静态变量
}
A.__index = A -- 当做格式即可function A:New(number) -- 定义构造函数,使用其它名称也是可以的-- self 类似 this 指针local obj = {}setmetatable(obj, self) -- 将对象 obj 的元表设置为类 Aobj.normalVar = number or 0 -- 使用 or 来制定默认值return obj
endfunction A:NormalMethod() -- 成员函数print("normalVar is " .. self.normalVar) -- 可以访问成员变量
endB = {} -- 继承类名为 B
B.__index = B -- 当做格式即可
setmetatable(B, A) -- B 继承了 Afunction B:New(number) -- 定义构造函数,使用其它名称也是可以的-- self 类似 this 指针local obj = A:New(number) -- 使用基类的构造函数setmetatable(obj, self) -- 将对象 obj 的元表设置为类 Bobj.normalVar2 = number + 1 or 0 -- 使用 or 来制定默认值return obj
endfunction B:NormalMethod() -- 成员函数print("normalVar2 is " .. self.normalVar2) -- 可以访问成员变量
endlocal b = B:New(1) -- 调用构造函数来创建对象
b:NormalMethod() -- 调用成员函数
输出结果为:
normalVar2 is 2
而不是:
normalVar is 1