Lua-8-面向对象的实现

要在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是指向对象自身的。因此多态就这么完成了。注意重写时千万别加冒号。

你可能感兴趣的:(lua基础,lua,开发语言)