要在lua中实现面向对象,我们需要面向对象三大特性:封装、继承、多态。
首先我们需要知道的是面向对象 类 其实都是基于 table来实现。
我们创建一个万类之父Object = { },并给予它一个属性Object.id = 1。再来对封装、继承、多态进行底层编写。
封装
封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。要访问该类的代码和数据,必须通过严格的接口控制。
lua中的冒号 是会自动的调用这个函数的对象 作为第一个参数传入的写法
Object = {}
Object.id = 1
function Object:Test()
print("Test的输出 "..self.id)
end
Object:Test()
---------------------------------
Test的输出 1
我们需要借助元表的内容来完成封装
function Object : new()
--self 代表的是 我们默认传入的第一个参数
--对象就是变量 返回一个新的变量
--返回出去的内容 本质上就是表对象
local obj = { }
--元表知识 __index 当找自己的变量找不到时 就会去找元表当中的__index指向的内容
self.__index =self
setmetatable(obj,self)
return obj
end
假如我们需要创建一个"类"的对象,则 local myObj = Object:new()。因为冒号的原因,Object作为第一个参数传入new()。此时新建obj空表,self为Object,__index指向自己。 以obj为子表,Object为父表建立联系。返回obj表,此时用 local myObj = Object:new()。就相当于创建了Object的对象myObj。实时上正确的叫法应该是 myObj 是Object子表obj的对象。这里obj是一个空表,调用空一般而言是没有申明意义的,但是在lua中元表的存在,再某种意义上可以让一个空表“充满”意义。
如下:需要注意的是,在 myObj.id =2之后,需要有一个疑惑,这里的id是Object的id,还是新建了myObj的属性id。从输出我们可以得到结果,Object.id依旧为1。此时再调用Test()传入自己作为参数,执行到print("Test的输出 "..self.id)时,此时myObj有id这个属性且为2。不需要去找元表的__index。
local myObj = Object:new()
print(myObj)
print("myObj的id,事实上是Object的id"..myObj.id)
myObj:Test()
myObj.id =2
print(Object.id)
myObj:Test()
---------------------------
table: 005C97F0
myObj的id,事实上是Object的id1
Test的输出 1
1
Test的输出 2
但是假如我们想使 myObj.id =2改变的是Object的id,从而步轻易给myObj添加属性,我们可以使用__newIndex()方法。
function Object : new()
--self 代表的是 我们默认传入的第一个参数
--对象就是变量 返回一个新的变量
--返回出去的内容 本质上就是表对象
local obj = { }
--元表知识 __index 当找自己的变量找不到时 就会去找元表当中的__index指向的内容
self.__index =self
self.__newindex= Object
setmetatable(obj,self)
return obj
end
local myObj = Object:new()
print(myObj)
print("myObj的id,事实上是Object的id"..myObj.id)
myObj:Test()
myObj.id =2
print(Object.id)
myObj:Test()
-------------------
table: 00A09430
myObj的id,事实上是Object的id1
Test的输出 1
2
Test的输出 2
可以看到改变的是myObj的元表__newIndex指向的表Object的内容。以上的前提是myObj没有id这个属性。
继承
--C# class 类名 : 继承类
--写一个用于继承的方法
知识点:G知识点 是总表 所有全局表里 都以键值对的形式存在其中,可以通过_G["x"] = xx 来创建全局表。
print("********G********")
print(_G)
_G["a"] = 1
_G.b = "123"
print(a)
print(b)
-----------------------
********G********
table: 00A02FA8
1
123
我们需要创建一个"类"(表)去继承一个父类(元表)。我们可以通过_G来实现它。
function Object:subClass(className)
--_G知识点 是总表 所有全局表里 都以键值对的形式存在其中
--通过_G创建空表
_G[className] = {}
--写相关继承的方法
--用到元表
local obj = _G[className]
self.__index = self
--定义base属性继承父类
obj.base = self
setmetatable(obj,self)
end
Object:subClass("Person")
print(Person)
print(Person.id)
local p1 = Person:new()
print(p1.id)
Object:subClass("Monster")
local m1 = Monster:new()
print(m1.id)
m1:Test()
---------------------
table: 00A19008
1
1
1
Test的输出 1
创建一个 Person"类"去继承一个"父类",具体流程如下:
第一步:Object:subClass("Person") 可能有人问,这不是已经有冒号了吗?为什么还要加一个Person参数。因为我们需要两个参数,一个参数是继承的父类,一个参数是创建Person类,千万要注意function Object:subClass(className)中,className不等于Object,它们是两个单独的参数。不然对于新手很容易迷糊(就是我了)。
第二步:"Person"代替了className,通过_G创建了一个全局表_G["Person"] = { }。
第三步:local obj = _G["Person"] 为了避免内存浪费,我们用一个local obj本地表存储Person表
第四步:self.__index = self 使元表的__index指向自己。此处self指Object
第五步: 再C#中我们继承之后 可以用base的方法继承父类。在此处我们自己定义一个base
obj.base = self
第六步:定义子表和元表setmetatable(obj,self)
此时Object:subClass("Person")完成 。print(Person)打印结果为table类型 及地址。print(Person.id)则是元表中Object.id。
local p1 = Person:new()
print(p1.id)
Person执行new()方法 得到的p1,p1是Person的子表。p1.id没有->Person.id没有->Object.id =1
详细步骤完毕。
多态
相同行为 不同表象 就是多态
相同方法 不同执行逻辑 就是多态
首先定义一个"类"GameObject继承Object。然后定义两个pos属性X、Y。
再定义一个Move()方法。最后定义一个Player“类”继承GameObject
Object:subClass("GameObject")
GameObject.posX = 0
GameObject.posY = 0
function GameObject:Move()
self.posX = self.posX+1
self.posY = self.posY+1
print(self.posX)
print(self.posY)
end
GameObject:subClass("Player")
我们重写Player的Move函数(注意这里有大坑)
function Player:Move( )
self.base:Move(self)
end
执行:
local p1 = Player:new()
p1:Move()
--目前这样的写法 有问题 不同对象使用的成员变量 居然是相同的成员变量
local p2 = Player:new()
p2:Move()
------------------------------------
**************多态**************
1
1
2
2
我们发现,按道理讲我创建一个Player对象p1,一个Player对象p2。它们的Move函数应该是单独的。来流程一下:
第一步:local p1 = Player:new(); p1是Player的子表。
第二步:p1:Move() ;调用p1的Move()函数,没有,由new()函数中的self.__index=self。因此去元表Player里找。Player的Move()函数为self.base:Move(self)。这里的self.base是表示Player的父类(元表)GameObject。具体实现可以看Object:subClass(className)中。
这里冒号传入的是GameObject,也就是Move(GameObject),因此两个输出相互不独立。
解决这个问题的办法就是将:变成 .
function Player:Move( )
self.base.Move(self)
end
local p1 = Player:new()
p1:Move()
local p2 = Player:new()
p2:Move()
----------------------------
1
1
1
1
为什么加.就能独立呢?
分析:self.base = GameObject -> GameObject的Move方法需要一个参数。
function GameObject:Move()
self.posX = self.posX+1
self.posY = self.posY+1
print(self.posX)
print(self.posY)
end
新手总是会下意识决定Move不需要参数,错误的想法凹。这不过是没写出来!
我们将GameObject:Move() 变成GameObject.Move(self) 此时的self是指向对象自身的。因此多态就这么完成了。注意重写时千万别加冒号。