前言
Lua是一种轻量级的脚本语言,在unity开发中经常用来做热更新相关的解决方案,我们现在的程序开发往往都是面向对象的,而Lua中是不存在类的概念的,让我们来看一下在Lua中如何实现面向对象编程。
lua中的一些方法:
- select('#', …) 返回可变参数的长度。
- select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。
一、Lua中的数据类型
Lua中共有8种数据类型,简单数据类型:number、boolean、string、nil,复杂数据类型:function、userdata、thread、table。Lua中没有类的概念,所有面向对象的实现都是基于table来实现的。在table中我们可以定义一些变量,去模拟一个类的封装,比如:
Student =
{
age = 18,
sex = true,
Up = function()
Student.age = Student.age + 1
print("长大了")
end,
Learn = function(t)
print(t.name .. "好好学习")
end
}
关于使用表中的成员,也和C#、C++等语言类似,但更像是类的静态成员,通过: 表名.成员变量名 调用,并且,可以在表外添加声明成员。而对于成员方法的调用分为 表名.成员方法() 和 表名:成员方法() 两种,前者是普通的调用,后者是将方法调用者作为第一个参数传入,方法中的self就代表着传入的第一个参数,对于上述表成员的调用,可看示例代码:
print(Student.age)
Student.Up()
print(Student.age)
//在表外声明成员变量
Student.name = "Happy.Q"
Student.Learn(Student)
Student:Learn()
//在表外声明成员方法
function Student:Speak()
print(self.name .. "说话")
end
Student:Speak()
}
运行结果:
二、_G
_G是一个Lua程序中的总表,也称为"大G表",它实际是一个table,里面存放了整个Lua程序中声明的所有全局变量,而本地变量不会被存放到大G表中。我们可以通过遍历大G表得到我们目前声明的所有全局变量。
代码如下:
for key,value in pairs(_G) do
print(key,value)
end
三、元表
元表的概念让table之间有了父子关系,任何一个表都可以设置为另一个表的元表,则另一个表就成为这个表的子表。设置元表的方法:
table1 = {}
table2 = {}
//设置元表函数,第一个参数:子表,第二个参数:元表
setmetatable(table1,table2)
元表中有一个很重要的特定操作:__index
首先注意,前面是两个下划线。__index的作用是,当在表中找不到索引时,会在元表的__index指向的表中寻找。通过一个简单的例子来看一下__index的作用:
table1 = {}
table2 =
{
age = 18
}
//__index的设置最好在表外
table2.__index = table2
//将table1设为table2的子表
setmetatable(table1,table2)
print(table1.age)
运行结果:
显然,table1中是不存在age的定义的,但通过它的元表table2的__index所指向的表,即table2自身,就能找到age。看到这里,熟悉面向对象的读者就会发现,这一特性类似于继承关系。关于元表,还有很多特定操作,比如__tostring,__tocall等,这里不做过多介绍,读者可以看Lua的官方文档进行学习,Lua的学习成本还是较低的,相信大家很容易就能学会。
四、面向对象
前面做了很多的铺垫,终于迎来了本文的重头戏——Lua中实现面向对象。面向对象的三大特性:封装、继承、多态。读完前面的内容,我们大概总结出来,表的定义可以实现封装,元表的概念和特性可以实现继承,其实多态也蕴含在表的方法中,下面让我们边实现面向对象,边去发现多态的存在。
1.封装
首先,我们实现一个类似C#中的万物之父——Object的存在
代码如下:
//万物之父 所有对象的基类
Object = {}
//为Object创建一个实例化对象的方法
function Object:new()
local obj = {}
//self就是第一个参数,即Object
//将__index指向Object。并将新定义的表设为Object的元表
//则新定义的表就有了Object中的属性
self.__index = self
setmetatable(obj,self)
return obj
end
这样,我们就完成了一个基本的万物之父的创建,我们便可以用Object的new方法实例化一个Object的对象:
local obj = Object:new()
2.继承
我们现在有了一个万物之父,那如何做到继承这个Object呢?在C#、C++中,是通过 类名:继承的类 来实现继承的,在Java中则是通过 类名 extends 继承的类 来实现的继承。我们在Lua中似乎不能通过这种符号或关键字的形式将两个表联系起来,所以我们还是考虑为Object来写一个方法实现继承。
代码如下:
function Object:subClass(className)
//为大G表添加一个名为className的表,就是创建了一个全局表
//表示新创建的类
_G[className] = {}
//将Object的__index指向自身
self.__index = self
//将Object作为新创建的表的元表
setmetatable(_G[className],self)
end
这样一来就实现了一个继承关系,结合上述封装,我们可以定义一个Person类,并实例化一个person对象:
//创建一个Person类,继承自万物之父Object
Object:subClass("Person")
//为Person类添加一个成员变量
Person.id = 10
//实例化一个person对象
person = Person:new()
print(person.id)
运行结果:
3.多态
前面已经实现了封装和继承,下面我们来看一下如何实现多态。我们先来想一下,其它面向对象的高级语言是如何实现的多态,这里我们只考虑子类对父类中虚方法的重写,以C#为例,我们是先在父类中用virtual修饰方法,再在子类中用override修饰重写同名方法,如果想基于父类方法重写,则需要用base关键字。在Lua中是不存在这些关键字的,那该怎么办呢?其实很简单,我们可以构造一个base,改一下Object中的继承方法:
function Object:subClass(className)
//为大G表添加一个名为className的表,就是创建了一个全局表
//表示新创建的类
_G[className] = {}
//将Object的__index指向自身
self.__index = self
//构建一个base,指向父类
_G[className].base = self
//将Object作为新创建的表的元表
setmetatable(_G[className],self)
end
这样,base就代表着父类,所以在子类重写方法时,还想继承父类的方法,就可以这样做:
function Person:Speak()
print(self.name.."说话")
end
Person:subClass("Student")
function Student:Speak()
//这一步很重要,不能写成:调用,否则传的self是self.base
//一定要这样写!!
self.base.Speak(self)
end
这里格外强调一下注释中的内容!这是个大坑,如果这里用了:调用方法,那么将把父类传到了方法中,出来的结果将是父类的name说话。这样,我们就完成了保留父类方法并进行重写,完成了多态的实现。
参考链接:
(105条消息) Lua语言实现面向对象