Lua从入门到入了门(5) OOP深入分析之风云版

原始实现

源码连接
原文

具体代码:

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
local _class={}

function class(super)
local class_type={}
class_type.ctor=false
class_type.super=super
class_type.new=function(...)
local obj={}
do
local create
create = function(c,...)
if c.super then
create(c.super,...)
end
if c.ctor then
c.ctor(obj,...)
end
end

create(class_type,...)
end
setmetatable(obj,{ __index=_class[class_type] })
return obj
end
local vtbl={}
_class[class_type]=vtbl

setmetatable(class_type,{__newindex=
function(t,k,v)
vtbl[k]=v
end
})

if super then
setmetatable(vtbl,{__index=
function(t,k)
local ret=_class[super][k]
vtbl[k]=ret
return ret
end
})
end

return class_type
end

测试

QuickX的Demo基本相同

将如上函数保存为ClassF.lua

再创建如下文件

AnimalClass.lua

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

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(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("ClassF")
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
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

[Override] CatClass called
Animal C1 eat with food : Fish

分析

Demo代码基本上和QuickX的那个相同,只是class定义初无需再传入className了,同时Cat.lua中注释掉了两行

1
2
--CatClass.super.ctor(self, _name)
--CatClass.super.SayHI(self, _msg)

ctor那个为无需手动调用,SayHI这个为无法实现.

下面我就来分析下原因

事先了解

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

等效转换

风神的代码初看是很懵圈的,老一代艺术家,在代码中沉浸了多年 随便抖两笔 所表达出的意境 都需要我等凡夫俗子 花许久 参悟参悟的😂😂😂

为了方便我等俗人理解,我就破坏一下美感把代码给改Low一下,方便研读一下😑

通俗版代码如下:

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

local mAllClassMetatableCoreCache = {}

function class(superFromArg)
local NewClass = {}
local NewClassMetatableCore = {}
mAllClassMetatableCoreCache[NewClass] = NewClassMetatableCore

NewClass.ctor = false
NewClass.super = superFromArg
NewClass.new = function(...)
local _newInstance = {}
do
local create
create = function(_class, ...)
if _class.super then
create(_class.super, ...)
end
if _class.ctor then
_class.ctor(_newInstance, ...)
end
end

create(NewClass, ...)
end
setmetatable(_newInstance, {__index = NewClassMetatableCore})
return _newInstance
end

setmetatable(NewClass, {__newindex =
function(t, k, v)
NewClassMetatableCore[k] = v
end
})

if superFromArg then
setmetatable(NewClassMetatableCore, {__index =
function(t, k)
local ret = mAllClassMetatableCoreCache[superFromArg][k]
NewClassMetatableCore[k] = ret
return ret
end
})
end

return NewClass
end

拆解分析

new函数

还是和之前一样 先来分析一下看似简单的new函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NewClass.new = function(...)
local _newInstance = {}
do
local create
create = function(_class, ...)
if _class.super then
create(_class.super, ...)
end
if _class.ctor then
_class.ctor(_newInstance, ...)
end
end

create(NewClass, ...)
end
setmetatable(_newInstance, {__index = NewClassMetatableCore})
return _newInstance
end

前半部分应该很好理解,就是一直递归到根Class然后逐层调用ctor. 有问题的其实是 看似平常的

1
setmetatable(_newInstance, {__index = NewClassMetatableCore})

这已经是被我翻译过一次的了,原始的代码是

1
setmetatable(obj,{ __index=_class[class_type] })

然后_classclass_type的定义均在前面.

还是回到我的low版代码吧,

需要注意的是 setmetatable(x, {__index = y})这种写法我之前有提过, 就是真大哥假大哥的问题

同时,此处相比于Quick代码中

1
2
3
4
5
6
7
function newClass.new(...)
local instance = {}
setmetatable(instance, newClass)
instance.class = newClass
instance:ctor(...)
return instance
end

直接把instanceclass进行绑定, 风神是吧instanceNewClassMetatableCore进行了绑定,(我特意起了一个比较诡异的名字,方便理解).

因为这部分没法单独说,所以暂时让我们先跳过new函数分析,看一下newClass的实现

class 定义部分

这部分分成两块,就是有super和没super的.

基类Class

先让我们来看没有Super的,就是例子中的AnimalClass.

1
2
3
4
5
setmetatable(NewClass, {__newindex =
function(t, k, v)
NewClassMetatableCore[k] = v
end
})

带入例子,这里面的NewClass就是对应的AnimalClass,而这段代码的意思呢 就是说 在往AnimalClass这张Table上添加任何key,vlaue时候呢 都通通给我添加到NewClassMetatableCore上来.

所以当后面我们去写Animal.lua文件

1
2
3
4
5
6
7
8

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

这些函数 其实没有任何一条是写在AnimalClass这张table上的

子类

AnimalClass怎么理解的, 我是把这个当做 “钥匙” 来理解的,并且还不是对自己,而是相对于CatClass

来看有super部分的代码实现

1
2
3
4
5
6
7
8
9
if superFromArg then
setmetatable(NewClassMetatableCore, {__index =
function(t, k)
local ret = mAllClassMetatableCoreCache[superFromArg][k]
NewClassMetatableCore[k] = ret
return ret
end
})
end

这个代码的意思呢,就是给 NewClassMetatableCore 绑定了 一个大哥,这个大哥呢还和之前的例子不同,不是一个不干事儿的大哥 而是一段方法.

这段代码 直接看有点绕, 让我们直接带入例子来看 这段时怎么工作的吧

首先拿出之前例子的中小c(c1)

c1是一个对象了 c1的真大哥呢 是 setmetatable(_newInstance, {__index = NewClassMetatableCore})中的{__index = NewClassMetatableCore}

把Cat带入就是 cat真大哥={__index = CatClassMetatableCore} , cat假大哥= CatClassMetatableCore

首先我们知道,当写Cat.lua时候

1
2
3
4
5
6
7
8
function CatClass:SayHI(_msg)
--CatClass.super.SayHI(self, _msg)
print("[Override] CatClass called")
end

function CatClass:GetEatFood()
return "Fish"
end

我们把SayHIGetEatFood两个方法写到 这个cat假大哥身上了. 但是我们没有写Eat这个方法. 所以当调用

1
c1:Eat()

首先c1先懵圈 不知道Eat是啥 然后他就去问他的 cat真大哥,真大哥大手一指 就让 小c 去他的cat假大哥身上找去.

cat假大哥 一听要找Eat 自己也不知道怎么整啊! 不过此时突然想起来了,自己是有super这个key的,所以定义的时候上帝也给自己找了一个大哥的!

1
2
3
4
5
6
7
8
9
10

if superFromArg then
setmetatable(NewClassMetatableCore, {__index =
function(t, k)
local ret = mAllClassMetatableCoreCache[superFromArg][k]
NewClassMetatableCore[k] = ret
return ret
end
})
end

翻译一下上面的代码就是

1
2
3
4
5

local cat假大哥的大哥 = {}
cat假大哥的大哥.__index = 一个查询方法

setmetatable(cat假大哥的大哥,cat假大哥的大哥)

我特意把里面的key换成了superFromArg,因为这块非常容易和自己身上的superkey混淆. 这个查询方法直接取得是类定义时候的superFromArg(closure闭包),和cat假大哥没有什么关系的,

cat假大哥看到的 就是一个普通方法而已

继续上面的分析就是,cat假大哥通过这个查询方法 找到了Eat这玩意,然后一路递归返回给小C,小C拿着Eat就去自己愉快的玩耍了.

注意这个查询方法里面有一行是

1
NewClassMetatableCore[k] = ret

这也就是风神自己博客下方有个兄弟留言说的Write On Use. 也就是 小C 第二次访问Eat操作时候,cat假大哥就知道Eat了,不会再通过查询方法mAllClassMetatableCoreCache这个大Table中进行查询了.

CatClass.super.SayHI(self, _msg) 问题

Quick版本中这行是可以执行的,也就是子类Override了父类,然后还要调用父类的这个方法. 这个调用方式在风神的版本中不能这样实现.

有意思的是,我无意中GoogleGoogle到一位大作家尝试着解决这个问题呢!

大作家也是个神人 先是在第一篇文章中云里来雾里去的分析了一通,然后因为所以科学道理 就直接得出了一些 很奇怪的 结论. 接着又在第二篇文章里面指出了这个无法调用Super的问题. 接着给出一个大作家版的实现.

抛开作家不说,底下有位仁兄应该也是遇到了这个问题, 然后给出了一个 他自己的递归解决法

其实风神这版实现是可以完成调用Super的,方法如下:

1
2
3
4
5
6
7
8
9
function CatClass:SayHI(_msg)
print("[Override] CatClass start")
local mtCat =getmetatable(self).__index
local mtCatMt =getmetatable(mtCat)
local superF = mtCatMt.__index(nil,"SayHI")
superF(self,_msg)
--CatClass.super.SayHI(self, _msg)
print("[Override] CatClass called")
end

用这个方法替换原来Cat的SayHI实现即可. 具体为啥这样写可以 ,我也学下大作家好了.

这个道理很简单,请读者自行思考一下即可. 来接着听我忽悠别的. 😂

anyway,我觉得如果有兴趣你可以自己尝试一下 为何这样写可以. 如果能推出来 说明我上面忽悠的那一堆还是有些用处的😎.

小结

风神这个OOP实现 对我来说最大的收获 就好比上学时候做对了卷子最后的附加题😂 让我对Lua似乎又明白了一些. 恩 恬不知耻的说 似乎感觉自己是 从入门到入了门…

至于代码层面上来说,相比Quick版 功能上多实现了一个Write On Use,然后构造函数是自动递归下去执行的 无需手动调用了. 不过这部分在Quick版本的代码中是比较好改造的. 详情参见我自己改造的版本

其他的 感觉就是将Class的Metatable藏得更深了一些,不过这么做的好处 我这个还在上学前班的小学童 目前还无法get到.

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