15.面向对象
15.1 知识点
封装
- Lua中的面向对象 类 其实都是基于表来实现 并且会结合元表相关的知识点
创建万物之父类 Object表
Object = {}
给万物之父手动添加实例化的new方法
-- 使用到的知识:在类外使用:在类外添加的成员方法
--由于Lua中的类(表)是类似c#中的静态类 没有实例化方法 所以我们要给万物之父手动添加实例化方法
--万物之父设置实例化方法
function Object:new()
--实例化方法的目的是返回一个新的对象 对象就是变量
--Lua中的对象可以理解为返回一个表变量
local obj = {} --要实例化的对象 表变量 防止全局定义 要是局部变量
setmetatable(obj, self) --设置万物之父是当前实例化对象的元表
self.__index = self --设置万物之父的__index表是万物之父自己 这样实例化对象找不到属性的时候可以去万物之父中找
return obj --返回对象
end
给万物之父设置一个属性id
Object.id = 1
给万物之父设置打印id属性的方法
function Object:PrintId()
print(self.id)
end
实例化对象并使用
local myObj = Object:new() --实例化对象
print(myObj) --table: 00BAA670 对象本质是表
print(myObj.id) --1 myObj其实是没有id属性的 是去myObj的元表Object的_index指向的Object中找的id属性
--模拟对象实例化后属性没有设置默认值 那么就是类中属性自带的默认值 这个默认值是去元表元表Object的_index指向的Object中找
myObj:PrintId() --1 myObj其实是没有PrintId方法的 是在PrintId方法中去myObj的元表Object的_index指向的Object中找的PrintId方法并调用
--模拟对象实例化调用类中封装的方法
myObj.id = 2 --给当前对象也设置一个属性叫id
print(Object.id) --1 仍然是1 不会改动到Object
print(myObj.id) --2 因为myObj设置了属性id 不会去找myObj的元表Object的_index指向的Object
--模拟对象实例化后属性设置了默认值 那就不去类中找默认值了 用自己赋值的
myObj:PrintId() --2 myObj其实仍然是没有PrintId方法的 但是传入了myObj作为参数 在PrintId方法中 因为myObj设置了属性id可以直接输出
--PrintId方法仍然用类公有的PrintId方法 但是值已经是自己设置的了
继承
回顾_G表
--继承要用到_G表的知识 _G本质上是一个表 以键值对的形式存储了所以全局变量
print(_G) --table: 00D54F00 _G本质上是一个表
-- 设置全局变量 a 的初始值为 111
_G["a"] = 111
print(a) -- 输出 111
-- 修改全局变量 a 的值为 222
_G.a = 222
print(a) -- 输出 222
-- 尝试将全局变量 a 的值作为键,设置 _G 表中的键为 222 的值为 333
-- 注意:这不会改变全局变量 a 的值
_G[a] = 333
print(a) -- 输出 222
print(_G[222]) -- 输出 333
-- 修改全局变量 a 的值为字符串 "a"
_G.a = "a"
print(a) -- 输出 "a",全局变量 a 的值是字符串 "a"
print(_G[a]) -- 输出 "a",这样写等同于 _G["a"]
给万物之父设置继承的方法 要传入子类名 要双引号包裹
function Object:subClass(className)
--继承的目的是设置传入表和调用继承方法表之前的继承关系 继承关系通过元表建立
--把传进来的子类 在_G中为传入来的类名设置全局变量 初始为空表
_G[className] = {}
--使用元表建立继承规则的关联
_G[className].base = self --base代表父类属性 self代表Object 设置子类的父类属性为Object
setmetatable(_G[className], self) --设置子类的元表是Object万物之父
self.__index = self --设置万物之父的__index表是万物之父自己 这样子类找不到的属性和方法可以去Object中找
end
子类继承万物之父Object
Object:subClass("Person") --子类Person继承万物之父Object
print(Person) --table: 00E5A4B8
local person1 = Person:new() --实例化Person
-- Person现在是空表 没有new方法
-- 去Person的元表Object的__index指向的表Object中找 发现有new方法
-- new方法会把Person作为第一个参数传进去 Person的__index设置成Person自己
-- 返回一个实例化Person的对象 并且元表是Person
print(person1.id) --1 person1现在是空表
-- 去元表Person的__index指向的表Person中发现也没有id 再上一层去Person的元表Object的__index指向的表Object中找 发现有id属性 返回id属性
person1.id = 100
print(person1.id) --100 person1表有了id属性
person1:PrintId() --100 person1没有:PrintId()方法 会一直找到Object发现有PrintId方法 传入自身person1作为参数 打印自身的id属性 刚刚设置了100
Object:subClass("Monster") --另一子类Monster和Person类似
local monster1 = Monster:new()
print(monster1.id) --1
monster1.id = 200
print(monster1.id) --200
monster1:PrintId() --200
多态
相同行为 不同表现 就是多态
相同方法 不同执行逻辑 就是多态
创建继承万物之父的GameObject类
Object:subClass("GameObject")
--设置GameObject的坐标属性
GameObject.posX = 0;
GameObject.posY = 0;
--设置GameObject移动方法
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
print(self.posX)
print(self.posY)
end
创建继承GameObject类的Player类
GameObject:subClass("Player")
--设置Player的移动方法
function Player:Move()
--Player继承GameObject 所以Player存在base属性 base是GameObject 表(类)
--Player正常会要调用基类GameObject的移动方法
--如果使用冒号调用GameObject的移动方法 会把GameObject作为第一个参数传入到移动方法中
--这样实例化的任何Player对象 共用同一张GameObject表的属性
--有点修改的是静态变量的意思 而不是对象自身的变量
-- self.base:Move()--存在问题
--如果要执行父类的逻辑 我们不要直接使用冒号调用 要通过.调用
--然后传入自己作为第一个参数
--GameObject的移动方法中的self意味着传入方法中的第一个参数
--这样在GameObject的移动方法中的self会是实例化出来的player
self.base.Move(self)
end
local player1 = Player:new()
player1:Move()--1 1
local player2 = Player:new()
player2:Move()--1 1 不会影响到player1
player1:Move()--2 2
15.2 知识点代码
print("**********面向对象************")
print("**********知识点一 封装************")
--Lua中的面向对象 类 其实都是基于表来实现 并且会结合元表相关的知识点
--创建万物之父类
Object = {}
--由于Lua中的类(表)是类似c#中的静态类 没有实例化方法 所以我们要给万物之父手动添加实例化方法
--万物之父设置实例化方法
function Object:new()
--实例化方法的目的是返回一个新的对象 对象就是变量
--Lua中的对象可以理解为返回一个表变量
local obj = {} --要实例化的对象 表变量 防止全局定义 要是局部变量
setmetatable(obj, self) --设置万物之父是当前实例化对象的元表
self.__index = self --设置万物之父的__index表是万物之父自己 这样实例化对象找不到属性的时候可以去万物之父中找
return obj --返回对象
end
--给万物之父设置一个属性id
Object.id = 1
--给万物之父设置打印id属性的方法
function Object:PrintId()
print(self.id)
end
local myObj = Object:new() --实例化对象
print(myObj) --table: 00BAA670 对象本质是表
print(myObj.id) --1 myObj其实是没有id属性的 是去myObj的元表Object的_index指向的Object中找的id属性
--模拟对象实例化后属性没有设置默认值 那么就是类中属性自带的默认值 这个默认值是去元表元表Object的_index指向的Object中找
myObj:PrintId() --1 myObj其实是没有PrintId方法的 是在PrintId方法中去myObj的元表Object的_index指向的Object中找的PrintId方法并调用
--模拟对象实例化调用类中封装的方法
myObj.id = 2 --给当前对象也设置一个属性叫id
print(Object.id) --1 仍然是1 不会改动到Object
print(myObj.id) --2 因为myObj设置了属性id 不会去找myObj的元表Object的_index指向的Object
--模拟对象实例化后属性设置了默认值 那就不去类中找默认值了 用自己赋值的
myObj:PrintId() --2 myObj其实仍然是没有PrintId方法的 但是传入了myObj作为参数 在PrintId方法中 因为myObj设置了属性id可以直接输出
--PrintId方法仍然用类公有的PrintId方法 但是值已经是自己设置的了
print("***********知识点二 继承************")
--回顾_G表
--继承要用到_G表的知识 _G本质上是一个表 以键值对的形式存储了所以全局变量
print(_G) --table: 00D54F00 _G本质上是一个表
-- 设置全局变量 a 的初始值为 111
_G["a"] = 111
print(a) -- 输出 111
-- 修改全局变量 a 的值为 222
_G.a = 222
print(a) -- 输出 222
-- 尝试将全局变量 a 的值作为键,设置 _G 表中的键为 222 的值为 333
-- 注意:这不会改变全局变量 a 的值
_G[a] = 333
print(a) -- 输出 222
print(_G[222]) -- 输出 333
-- 修改全局变量 a 的值为字符串 "a"
_G.a = "a"
print(a) -- 输出 "a",全局变量 a 的值是字符串 "a"
print(_G[a]) -- 输出 "a",这样写等同于 _G["a"]
--给万物之父设置继承的方法 要传入子类名 要双引号包裹
function Object:subClass(className)
--继承的目的是设置传入表和调用继承方法表之前的继承关系 继承关系通过元表建立
--把传进来的子类 在_G中为传入来的类名设置全局变量 初始为空表
_G[className] = {}
--使用元表建立继承规则的关联
_G[className].base = self --base代表父类属性 self代表Object 设置子类的父类属性为Object
setmetatable(_G[className], self) --设置子类的元表是Object万物之父
self.__index = self --设置万物之父的__index表是万物之父自己 这样子类找不到的属性和方法可以去Object中找
end
Object:subClass("Person") --子类Person继承万物之父Object
print(Person) --table: 00E5A4B8
local person1 = Person:new() --实例化Person
-- Person现在是空表 没有new方法
-- 去Person的元表Object的__index指向的表Object中找 发现有new方法
-- new方法会把Person作为第一个参数传进去 Person的__index设置成Person自己
-- 返回一个实例化Person的对象 并且元表是Person
print(person1.id) --1 person1现在是空表
-- 去元表Person的__index指向的表Person中发现也没有id 再上一层去Person的元表Object的__index指向的表Object中找 发现有id属性 返回id属性
person1.id = 100
print(person1.id) --100 person1表有了id属性
person1:PrintId() --100 person1没有:PrintId()方法 会一直找到Object发现有PrintId方法 传入自身person1作为参数 打印自身的id属性 刚刚设置了100
Object:subClass("Monster") --另一子类Monster和Person类似
local monster1 = Monster:new()
print(monster1.id) --1
monster1.id = 200
print(monster1.id) --200
monster1:PrintId() --200
print("**********知识点三 多态************")
--相同行为 不同表现 就是多态
--相同方法 不同执行逻辑 就是多态
--创建继承万物之父的GameObject类
Object:subClass("GameObject")
--设置GameObject的坐标属性
GameObject.posX = 0;
GameObject.posY = 0;
--设置GameObject移动方法
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
print(self.posX)
print(self.posY)
end
--创建继承GameObject类的Player类
GameObject:subClass("Player")
--设置Player的移动方法
function Player:Move()
--Player继承GameObject 所以Player存在base属性 base是GameObject 表(类)
--Player正常会要调用基类GameObject的移动方法
--如果使用冒号调用GameObject的移动方法 会把GameObject作为第一个参数传入到移动方法中
--这样实例化的任何Player对象 共用同一张GameObject表的属性
--有点修改的是静态变量的意思 而不是对象自身的变量
-- self.base:Move()--存在问题
--如果要执行父类的逻辑 我们不要直接使用冒号调用 要通过.调用
--然后传入自己作为第一个参数
--GameObject的移动方法中的self意味着传入方法中的第一个参数
--这样在GameObject的移动方法中的self会是实例化出来的player
self.base.Move(self)
end
local player1 = Player:new()
player1:Move()--1 1
local player2 = Player:new()
player2:Move()--1 1 不会影响到player1
player1:Move()--2 2
15.3 练习题
用lua实现面向对象,定义动物类,动物类有名字和说话方法,定义具体实现的狗类和猫类
定义万物之父基类
Object = {}
setmetatable(Object, Object)
Object.__index = Object
function Object:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
function Object:subClass(className)
_G[className] = {}
_G[className].base = self
setmetatable(_G[className], self)
self.__index = self
end
function Object:toString()
return tostring(self)
end
object1 = Object:new()
print(object1:toString())
定义动物类,重写new方法
Object:subClass("Animal")
function Animal:new(animalName)
local obj = Animal.base.new(self)
obj.animalName = animalName
return obj
end
function Animal:Speak()
print("动物"..self.animalName.."开始叫")
end
定义狗类,重写说话方法
Animal:subClass("Dog")
function Dog:Speak()
self.base.Speak(self)
print("狗"..self.animalName.."开始旺旺叫")
end
dog1 = Dog:new("Spike")
dog1:Speak()
定义猫类,重写说话方法
Animal:subClass("Cat")
function Cat:Speak()
self.base.Speak(self)
print("猫"..self.animalName.."开始喵喵叫")
end
cat1 = Cat:new("Tom")
cat1:Speak()
运行打印结果
table: 00C297C8
动物Spike开始叫
狗Spike开始旺旺叫
动物Tom开始叫
猫Tom开始喵喵叫
15.4 练习题代码
print("**********面向对象练习题************")
Object = {}
setmetatable(Object, Object)
Object.__index = Object
function Object:new()
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
function Object:subClass(className)
_G[className] = {}
_G[className].base = self
setmetatable(_G[className], self)
self.__index = self
end
function Object:toString()
return tostring(self)
end
object1 = Object:new()
print(object1:toString())
Object:subClass("Animal")
function Animal:new(animalName)
local obj = Animal.base.new(self)
obj.animalName = animalName
return obj
end
function Animal:Speak()
print("动物"..self.animalName.."开始叫")
end
Animal:subClass("Dog")
function Dog:Speak()
self.base.Speak(self)
print("狗"..self.animalName.."开始旺旺叫")
end
dog1 = Dog:new("Spike")
dog1:Speak()
Animal:subClass("Cat")
function Cat:Speak()
self.base.Speak(self)
print("猫"..self.animalName.."开始喵喵叫")
end
cat1 = Cat:new("Tom")
cat1:Speak()
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com