Lua从入门到入了门(4) OOP深入分析之QuickX版

原始实现

quick-cocos2d-x版

functions.lua函数281行开始 给出了class的实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

function class(classname, super)
local superType = type(super)
local cls

if superType ~= "function" and superType ~= "table" then
superType = nil
super = nil
end

if superType == "function" or (super and super.__ctype == 1) then
-- inherited from native C++ Object
cls = {}

if superType == "table" then
-- copy fields from super
for k,v in pairs(super) do cls[k] = v end
cls.__create = super.__create
cls.super = super
else
cls.__create = super
cls.ctor = function() end
end

cls.__cname = classname
cls.__ctype = 1

function cls.new(...)
local instance = cls.__create(...)
-- copy fields from class to native object
for k,v in pairs(cls) do instance[k] = v end
instance.class = cls
instance:ctor(...)
return instance
end

else
-- inherited from Lua Object
if super then
cls = {}
setmetatable(cls, {__index = super})
cls.super = super
else
cls = {ctor = function() end}
end

cls.__cname = classname
cls.__ctype = 2 -- lua
cls.__index = cls

function cls.new(...)
local instance = setmetatable({}, cls)
instance.class = cls
instance:ctor(...)
return instance
end
end

return cls
end

测试

将如上函数保存为ClassQ.lua

再创建如下文件

Animal.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AnimalClass = class("AnimalClass")

function AnimalClass:ctor(_name)
print("call animal ctor name is: ".._name)
self.mName = _name
self.mFood = "normal food"
end

function AnimalClass:SayHI(_msg)
print(string.format("Animal %s say hi with msg : %s", self.mName, _msg))
end

function AnimalClass:Eat()
print(string.format("Animal %s eat with food : %s", self.mName, self:GetEatFood()))
end

function AnimalClass:GetEatFood()
return self.mFood
end

Cat.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CatClass = class("CatClass",AnimalClass)

function CatClass:ctor(_name, _color)
CatClass.super.ctor(self, _name)
print(string.format("call cat ctor name is: %s color is %s", _name, _color))
self.mColor = _color
end

function CatClass:SayHI(_msg)
CatClass.super.SayHI(self, _msg)
print("[Override] CatClass called")
end

function CatClass:GetEatFood()
return "Fish"
end

入口文件main.lua

1
2
3
4
5
6
7
8
9
10
11
require("ClassQ")
require("Animal")
require("Cat")

a1 = AnimalClass.new("A1")
a1:SayHI("oh~")
a1:Eat()

c1 = CatClass.new("C1","Red")
c1:SayHI("oh~")
c1:Eat()

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
call animal ctor name is: A1
Animal A1 say hi with msg : oh~
Animal A1 eat with food : normal food

call animal ctor name is: C1
call cat ctor name is: C1 color is Red

Animal C1 say hi with msg : oh~
[Override] CatClass called

Animal C1 eat with food : Fish

分析

删减

QuickX有一部分是为了要考虑直接和引擎C++层绑定,这部分不在我们的讨论范围中. 所以将其class函数中相关部分代码去掉.

最终代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function class(classname, super)

local cls

-- inherited from Lua Object
if super then
cls = {}
setmetatable(cls, {__index = super})
cls.super = super
else
cls = {ctor = function() end}
end

cls.__cname = classname
cls.__index = cls

function cls.new(...)
local instance = setmetatable({}, cls)
instance.class = cls
instance:ctor(...)
return instance
end

return cls
end

等效转换

接着和之前一样 我们再把这部分代码 改些名字,去除个语法糖 等效 的改成如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function class(classname, super)

local newClass

-- inherited from Lua Object
if super then
newClass = {}
setmetatable(newClass, {__index = super})
newClass.super = super
else
newClass = {ctor = function() end}
end

newClass.__cname = classname
newClass.__index = newClass

function newClass.new(...)
local instance = {}
setmetatable(instance, newClass)
instance.class = newClass
instance:ctor(...)
return instance
end

return newClass
end

拆解分析

事先了解

如果你对为何能等效转换及2B情景法有疑问,请看我之前的文章: Lua从入门到入了门(2) 基础难点概述

new 函数

首先要拆开了来看, 先看new函数部分

1
2
3
4
5
6
7
8
9
10
11
12
13

--函数定义
function newClass.new(...)
local instance = {}
setmetatable(instance, newClass)
instance.class = newClass
instance:ctor(...)
return instance
end

--调用方法
a1 = AnimalClass.new("A1")
c1 = CatClass.new("C1","Red")

这部分很简答, 就是把小弟和大哥关联了一下. 顺便调用了一下小弟的ctor函数 让其初始化以下.

class 定义部分

比较绕的是class定义部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

--函数定义

function class(classname, super)

local newClass

-- inherited from Lua Object
if super then
newClass = {}
setmetatable(newClass, {__index = super})
newClass.super = super
else
newClass = {ctor = function() end}
end

newClass.__cname = classname
newClass.__index = newClass

return newClass
end

--调用方法

AnimalClass = class("AnimalClass")
CatClass = class("CatClass",AnimalClass)
AnimalClass

定义小a的大哥AnimalClass时候,大哥就是一个含有ctor函数的空表,注意此时AnimalClass还不是小a的大哥,只是一张普通的table而已. 所以

1
newClass.__index = newClass

这行定义 只是找了一个名字叫__index的Key,让其值又引用了其自身, 只有当创建小a时候

1
a1 = AnimalClass.new("A1")

此时AnimalClass才成了小a的大哥,AnimalClass.__index=AnimalClass才等效与之前例子中的GreenPaper(在之前的教程中)

CatClass

CatClass有别与AnimalClass,定义时候是有super的. 所以就是相当于 设置了 大哥的大哥.

1
2
3
4
5

--调用这行
CatClass = class("CatClass",AnimalClass)
--带入则变为
setmetatable(CatClass, {__index = AnimalClass})

所以当小c(c1) 调用某个函数时候 首先问其大哥CatClass,

c1其实只包含了数据,都是 通过 self.mColor=xxx 这种在某些函数中定义的,自身是不包含任何函数的. 所以外面一调用c1:SayHI() 这种函数 c1中肯定是没有的,所以就会去向其大哥求救

大哥查了一下自己发现自己也搞不定

大哥之所以会查自己,是因为上面newClass.__index = newClass设置的,大哥自己能搞得定的情况就是说明子类Override了父类的方法了.也就是大哥中有这个实现. 比如例子中的CatClass:SayHI. 没有实现的 比如CatClass:Eat 就是大哥自己也搞不定的情况了.

大哥(CatClass)搞不定就去问了其大哥的大哥AnimalClass

CatClass会去问AnimalClass 是因为设置了setmetatable(newClass, {__index = super}) , 这部分有点绕 涉及到点有几个

  1. CatClass是c1的元表,但是其自身也是表,他也是可以有元表的

  2. CatClass的元表 并不是 AnimalClass 😎 惊不惊喜?意不意外? 详情可以看我之前的文章, 就是真大哥,假大哥那块.

  3. CatClass只有自己查不到的情况下才会向AnimalClass求救的. 这也是我之前一直比较懵圈的地方. 用2B情景法来描述就是

小弟(c1)先自己解决,解决不了找大哥(CatClass)
小弟(CatClass)先自己解决,解决不了找大哥(AnimalClass)

这样一层一层就递归下去了

小结

Quick这个实现版本基本上来说已经够用了,不过调用时候有几个点要注意

new函数需要用 .的方式来调用,其他函数却需要用:来调用

1
2
3
4
5

--正确的
a1 = AnimalClass.new("A1")
--错误的
a1 = AnimalClass:new("A1")

原因是因为其new函数声明时候用的点,内部instance用的是冒号,这样用冒号去调用new函数时候会多往ctor中传一个”self”(这个self其实是Class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

a1 = AnimalClass:new("A1")
--等阶与
a1 = AnimalClass.new(AnimalClass,"A1")

--new函数内部调用
instance:ctor(...)
--等阶与
instance.ctor(instance,...)

--带入上面的a1和实际参数则为
a1.ctor(a1,AnimalClass,"A1")

--但是Animal的ctor定义为
function AnimalClass:ctor(_name)

--所以用AnimalClass:new("A1")调用的话 _name得到的是AnimalClass,"A1"这个参数就丢失了

可以实现子类重载后还能调用父类的方法只不过实现起来看着会比较诡异

比如Cat中的SayHI函数

1
CatClass.super.SayHI(self, _msg)

理解起来其实倒是不难, CatClass.super 也就是 AnimalClass

也就变成了

AnimalClass.SayHI(self, _msg)

此时self是c1 , 不能直接 AnimalClass:SayHI(_msg) ,因为这样就没把c1传过去. 相当于表儿传错了

坚持原创技术分享,您的支持将鼓励我继续创作!