第四章:面向对象
0. 导入
通过table
、function
和元表模拟面向对象。简单对象创建时,冒号隐式传递self
,点号显式传递。类通过new
方法创建实例,元表_index
实现继承。实例修改不影响基类。继承时,子类调用父类方法需注意self
和参数传递,不同调用方式影响继承链和属性查找。对象间通过引用共享数据,修改会相互影响。
1. 简单对象的创建
情景引入:
animal = {name = "Tom", age = 5}
animal.bark = function (type)
print(animal.name .. " is a " .. type)
end
animal.bark("dog") -- Tom is a dog
创建一个animal_1
“对象”,使其等于animal
,此时它们指向同一个内存空间,实际就是一个对象:
animal_1 = animal
animal_1.name = "Jerry"
animal_1.bark("cat") -- Jerry is a cat
如果删除animal
,animal_1
还会继续存在,但是此时调用会报异常:
animal = nil
animal_1.bark("cat") -- error: attempt to index a nil value (global 'animal')
因为此时animal.bark
里传的是固定参数animal
,这时候是nil报错,可以如下修改:
animal.bark = function (self, type)
print(self.name .. " is a " .. type)
end
-- 在调用时增加参数:
animal_1.bark(animal_1, "cat") -- Jerry is a cat
此时animal
成为了我们熟知的“对象”,self是显式的呈现出来,也可以使用冒号(lua的语法糖):
function animal:bark(type)
print(self.name .. " is a " .. type)
end
-- 在调用时无需加参数,但是要使用冒号:
animal_1:bark("cat") -- Jerry is a cat
animal_1.bark(animal_1, "cat") -- Jerry is a cat
总结:冒号是隐式self,点号是显式self,只在调用和定义时入参有区分,其他没变化。
但是,这种创建对象,始终都是同一个对象,如果需要创建不同对象如何处理呢?
2. 类的创建
Lua中使用table
、function
与元表
可以定义出类:
- 使用一个
table
作为基类,创建该基类的new()
方法; - 该
new()
方法中创建一个空表,并返回这个空表; - 为该空表指定一个元表:该元表重写
__index
元方法,且将基类指定为重写的__index元方法.
至此,类即创建出来。
-- 创建一个基类
Animal = {name = "no_name", age = 5}
function Animal:bark(type)
print(self.name .. " is a " .. type)
end
-- 创建该基类的new()方法:
function Animal:new()
-- 创建一个空表
local a = {}
-- 指定元表,将基类赋值给__index元方法
setmetatable(a, {__index = self})
-- 返回这个空表
return a
end
还是那句话:__index
元方法,只影响get,不影响set,所以在对象中找不到的,会去基类中找,在对象中set的动作不会影响基类。
思考:为啥会去基类中找?
因为找不到会去元表的__index
元方法中找,因为赋值的就是基类,所以会去基类中找。
创建两个实例:
animal1 = Animal:new()
animal2 = Animal:new()
print(animal1) -- table: 0000000000f898d0
print(animal2) -- table: 0000000000f89ed0
打印这两个实例的地址,可以看到是不同的对象。
animal1.name = "Tom"
animal1.age = 6
animal1.type = "Cat"
animal1:bark(animal1.type) -- Tom is a Cat
print(Animal.name) -- no_name
print(Animal.type) -- nil
给实例animal1赋值,不会对基类有影响。
3. 类的继承
以下示例展示类的继承特性。
-- 创建一个有参构造器
local people = {name = "unknow", age = 0}
function people:new(obj)
local p = obj or {}
setmetatable(p, {__index = self})
return p
end
-- 创建一个入参
local Tom = {name = "Tom"}
-- 创建一个对象
local p_Tom = people:new(Tom)
p_Tom.gender = "male"
print("p_Tom.name = " .. p_Tom.name .. ", p_Tom.age = " .. p_Tom.age .. ", p_Tom.gender = " .. p_Tom.gender)
print("-------------------------")
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom.age = " .. Tom.age)
print("-------------------------")
p_Tom.name = "Jerry"
p_Tom.age = 5
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom.age = " .. Tom.age)
输出:
p_Tom.name = Tom, p_Tom.age = 0, p_Tom.gender = male
-------------------------
p_Tom.name = Tom, Tom.name = Tom
p_Tom.age = 0, Tom.age = 0
-------------------------
p_Tom.name = Jerry, Tom.name = Jerry
p_Tom.age = 5, Tom.age = 5
可以看到,p_Tom
是继承people
,p_Tom
和Tom
指向同一个内存,p_Tom
变化,Tom
也会变化:
print(p_Tom, Tom) -- table: 0000000000e99d10 table: 0000000000e99d10
其实p_Tom
是对象也是类,可以使用构造函数new()
构造子类(对象)。
-- 创建一个子类继承p_Tom,这里p_Tom没有new方法,因此会去父类people中找
Tom1 = p_Tom.new()
Tom1.name = "Jack"
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name .. ", Tom1.name = " .. Tom1.name)
-- print("p_Tom.age = " .. p_Tom.age .. ", Tom1.age = " .. Tom1.age) -- attempt to concatenate a nil value (field 'age')
输出:
p_Tom.name = Jerry, Tom.name = Jerry, Tom1.name = Jack
但是如果传参呢?
Tom1 = p_Tom.new(p_Tom)
Tom1.name = "Jack"
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name .. ", Tom1.name = " .. Tom1.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom1.age = " .. Tom1.age)
输出:
p_Tom.name = Jerry, Tom.name = Jerry, Tom1.name = Jack
p_Tom.age = 5, Tom1.age = 5
为什么会这样呢?
首先要搞清楚p_Tom.new()
是如何调用的:p_Tom
本身没有new()
方法,由于p_Tom
继承people
,所以p_Tom
会向上调用people
的new()
方法。
注意这里p_Tom.new()
是点号不是冒号,调用p_Tom
的new()
方法相当于people.new()
,即people.new(nil, nil)
,注意此时的self
就是nil
而不是people
,因此,Tom1 = p_Tom.new()
的Tom1
没有父类,Tom1.age
是未定义。
print(p_Tom, Tom1, people) -- table: 0000000000f29f10 table: 0000000000f29bd0 table: 0000000000f29a90
可以看到三个对象地址均不同。
若是p_Tom.new(p_Tom)
,则调用关系如下:people.new(p_Tom, nil)
,,注意此时self
就是p_Tom
而不是people
,Tom1
父类是p_Tom
,所以Tom1.age
返回的是p_Tom.age
的值5。
若是如下呢?
Tom1 = p_Tom.new(p_Tom, people)
Tom1.name = "Jack"
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name .. ", Tom1.name = " .. Tom1.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom1.age = " .. Tom1.age)
输出:
p_Tom.name = Jerry, Tom.name = Jerry, Tom1.name = Jack
p_Tom.age = 5, Tom1.age = 0
其实p_Tom.new(p_Tom, people)
调用关系如下:people.new(p_Tom, people)
,此时self
就是p_Tom
,且obj
是people
,因此Tom1
就是people
,所以Tom1.age
返回的是people.age
的值0。
print(p_Tom, Tom1, people) -- table: 0000000000f49990 table: 0000000000f49bd0 table: 0000000000f49bd0