本篇文章,主要来讲述,lua中的面向对象是如何实现的。
1. 对象-table
lua中的对象,其实之前在介绍类型的时候就稍微提及过一些 ———— table。
有三个理由,让table可以胜任对象
- table与对象一样可以拥有状态
- table与对象一样拥有一个独立与其值的标识(self)
- table与对象一样具有独立于创建者和创建地的生命周期
我们要在一个对象中,设置一个函数:
Person = {name = "Tom"}
function Person.changeName(newName)
Person.name = newName
end
Person.changeName("tree")
print(Person.name)
-- result
tree
这样的函数就是方法,但是,如果方法这么写,是非常不好的。
因为如果我们讲对象名字换掉了,那么相应的方法就不能用了:
p = Person
Person = nil
p.changeName("Jerry")
print(p.name)
-- result
nil
这违反了之前的第三条——对象拥有独立的生命周期。
这时候,就要用到self了,它跟C++中的this指针相像。
function Person.changeName(self, newName)
self.name = newName
end
p = Person
Person = nil
p.changeName(p, "Jerry")
print(p.name)
-- result
Jerry
但是,每次调用的时候,都要显式的把相应对象传进去,是不是很麻烦呢?
大多数面向对象的语言都能对程序员隐藏部分self参数,lua也不例外,它只需要用冒号:
function Person:changeName(newName)
self.name = newName
end
...
p:changeName("Jerry")
就是这么简单。
2.类
lua中没有类的概念,每个对象只能自定义行为和形态。
不过,要在lua中模拟类也并不困难,可以参照一些基于原型的语言,
基于原型的语言中,对象是没有”类型”的,而是每个对象都有一个原型,原型也是一种常规的对象。
当对象执行一个未知操作时,原型会先查找它。
在lua中的实现,用到了元表(因为查找一个,找不到查找另一个 -> 这种行为,很典型的元表)
-- 一个原型
local Person = {}
function Person:new(t)
t = t or {}
setmetatable(t, self)
self.__index = self
return t
end
local p = Person:new({name = "Tom"})
p:showName()
我们,首先用Person:new创建了一个新的实例对象,然后将Person作为新的实例对象p的元表。
再当我们调用 p:showName 函数时就会查找p中是否有showName字段,如果没有,就去搜索它的元表
一个类不仅可以提供方法,还可以为实例中的字段提供默认值:
local Person = {name = "Tom", age = 0}
function Person:new(t)
t = t or {}
setmetatable(t, self)
self.__index = self
return t
end
function Person:showDetail()
self.age = self.age + 1
print("name: "..self.name.." ,age: "..self.age)
end
local p = Person:new()
p:showDetail()
p:showDetail()
在Person表中有一个 age的字段,我们默认为0;当我们创建一个实例对象,没有提供关于age的字段,在showDetail函数中,由于p中没有age字段,向上查找到Person,最后得到的是Person的age值。
3.继承
因为类也是对象,所以它也可以从其它类获得方法。
假设,我们有个类Person,然后要派生出一个类Student:
local Person = {name = "Tom", age = 0}
function Person:new(t)
t = t or {}
setmetatable(t, self)
self.__index = self
return t
end
function Person:setAge(age)
self.age = age
end
function Person:setName(name)
self.name = name
end
function Person:showDetail()
print("Name: "..self.name..",Age: "..self.age)
end
local Student = Person:new() -- 1
local s = Student:new({name = "Jerry"}) -- 2
s:showDetail()
在这段代码中,我们在开始构建一个”基类” Person,给它设置一些方法(字段)
然后,在 1 中,我们创建了Person的实例对象——Student,但是Student也是一个类。
在 2 中,我们用Student派生出s。
这时,s执行showDetail函数,当在s中找不到相应字段,因为s继承自Student,便会去Student去寻找,当在Student也找不到时,会寻找Student的父类——Person。
但是,如果我们在s或者Student重新定义了一个showDetail函数,它便会覆盖父类的,不会继续往下找。
这,就是继承体系。
4.多重继承
多重继承,简单的了解一下。
我们实现单一继承的方法,是利用了lua中的元表,在派生类中找不到字段,设置metatable,让其去父类寻找,并将父类的__index设置为自身。
多继承也可以这样用,继承,就是为了利用父类相应的方法,多继承,就是在多个父类中寻找相应的方法(字段),我们可以将__index设置一个函数,让它遍历所有父类。
5.权限
在面向对象中,我们的类成员是有访问权限的。
最基础的: private、protect、public
lua是一个简单的脚本语言,它尽量的压缩自己的代码,显然本身不会带这些机制。
但,没有枪、没有炮、我们自己造啊~
function student(name)
local self = {m_name = name}
local setName = function (name)
self.m_name = name
end
local getName = function ()
return self.m_name
end
return {setName = setName, getName = getName}
end
local stu = student("Tom")
print(stu.getName())
stu.setName("Jerry")
print(stu.getName())
这里,我们创建一个函数,然后在函数内部,自己构建一套机制来设置、获取,返回相应的方法。
当我们获得 student的返回以后,我们就无法直接访问这个table,只能通过函数来访问。
用这种方法来保护类成员