Lua之middleclass详解

A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
https://github.com/kikito/middleclass

前言

本文逐步的分析了middleclass模拟oop编程的过程。但是建议读者在看这篇文章之前对Lua的元表和元方法有一定的了解。

类定义

新建类定义

创建一个modulde,创建一个空白类定义对象,类定义对象需记录自己的name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---middleclass---
local middleclass = {}

local function _createClass(name)
local aClass = {
name = name, -- 类名
}
return aClass
end

function middleclass.class(name)
return _createClass(name)
end

return middleclass

测试:

1
2
3
4
5
6
7
8
---Animal---
local Class = require 'middleclass'

local Animal = Class.class('Animal')

print(Animal) -- table: 00B99B68

return Animal

添加__tostring函数

目前执行print(Animal)的时候,输出的是 table: 00B99B68 (类型: 指针地址)
可以让print的时候输出更友好的内容,比如 class Animal

解决方案
给新建类统一添加__tostring函数
print(Animal)时,会隐式调用tostring(Animal),接着会尝试调用getmetatable(Animal).__tostring(Animal)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---middleclass---
local middleclass = {}

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local aClass = {
name = name, -- 类名
}

setmetatable(aClass, {
__tostring = _tostring, --设置aClass的元方法__tostring
})

return aClass
end

function middleclass.class(name)
return _createClass(name)
end

return middleclass

测试:

1
2
3
4
5
6
7
8
---Animal---
local Class = require 'middleclass'

local Animal = Class.class('Animal')

print(Animal) -- class Animal

return Animal

模拟Class关键字

目前创建一个类定义对象要调用如下写法Class.class(classname)
可以改为模拟oop Class关键字的风格Class(classname)

解决方案
给middleclass添加__call函数
把一个table当做函数调用时,会去尝试执行该table的metatable中的__call函数

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

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local aClass = {
name = name, -- 类名
}

setmetatable(aClass, {
__tostring = _tostring, --设置aClass的元方法__tostring
})

return aClass
end

function middleclass.class(name)
return _createClass(name)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass

测试:

1
2
3
4
5
6
7
8
---Animal---
local Class = require 'middleclass'

local Animal = Class('Animal')

print(Animal) -- class Animal

return Animal

实例化

导入

A继承B的效果是什么?我们粗略的理解为,A不经显式声明就可以使用B的函数/变量。
middleclass使用了一个粗暴的手段:把父类中的所有属性都拷贝到子类中。middleclass没有把这种手段称为【继承】,而是借用了另外一个术语【导入(Include)】,这时父类被称为【Mixin】。

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

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local aClass = {
name = name, -- 类名
}

setmetatable(aClass, {
__tostring = _tostring, --设置aClass的元方法__tostring
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
for name,method in pairs(mixin) do
if name ~= 'included' then
aClass[name] = method
end
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

function middleclass.class(name)
return _createClass(name)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass

实例化

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

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local aClass = {
name = name, -- 类名
}

setmetatable(aClass, {
__tostring = _tostring, --设置aClass的元方法__tostring
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
for name,method in pairs(mixin) do
if name ~= 'included' then
aClass[name] = method
end
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

--创建实例
local function allocate(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return { class = aClass, initialize = function (aClass, ...) end }
end

--创建实例并初始化
local function new(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end

function middleclass.class(name)
return _createClass(name)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass

DefaultMixin

目前middleclass出现了两类函数:工具函数和class专用函数
class专用函数,比如,每个新创建的class都应拥有实例化的能力。class专用函数被打包到一个叫DefaultMixin表中,通过include方便的复制给每个新建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
61
62
63
64
65
66
---middleclass---
local middleclass = {}

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local aClass = {
name = name, -- 类名
}

setmetatable(aClass, {
__tostring = _tostring, --设置aClass的元方法__tostring
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
for name,method in pairs(mixin) do
if name ~= 'included' then
aClass[name] = method
end
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

local DefaultMixin = {
--初始化
initialize = function(instance, ...) end,

--创建实例
allocate = function(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return { class = aClass, initialize = aClass.initialize }
end,

--创建实例并初始化
new = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end,
}

function middleclass.class(name)
return _includeMixin(_createClass(name), DefaultMixin)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass
1
2
3
4
5
6
7
8
9
10
---Animal---
local Class = require 'middleclass'

local Animal = Class('Animal')

function Animal:initialize(name)
print('Animal initialize name:'.. name)
end

return Animal

测试:

1
2
3
4
5
6
---test---
local Animal = require 'Animal'

local dog = Animal:new('dog') -- Animal initialize name:dog

return dog

静态vs成员

lua本身没有静态与成员函数之分。
middleclass则定义的更为细致,更为贴近oop惯例。

classinstance
静态函数可调用不可调用
成员函数可调用(有什么意义?)可调用
  • class可调用成员函数的意义? 必不可少的功能,不然怎么通过super来调用父类的成员函数。为实现上述目的,middleclass对某class上定义的函数进行了分组
  • 也就是说,函数的声明都是在class级别进行的
    1. class.static : 只有class可以调用的函数
    2. class.__instanceDict : instance所调用的函数

static表

上面章节的class专用函数,比如实例化函数,middleclass将其当做静态函数处理;在别的语言中,实例化功能可能埋藏在语言实现的底层,以语言关键字的形式出现,而非静态函数。是的,关键就在于此:middleclass用静态函数来模拟new这个关键字。
middleclass用class.static来记录class中的静态函数,并将class.static设为class元表中__index的值中。class得以调用class.static中的函数

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
---middleclass---
local middleclass = {}

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local aClass = {
name = name, -- 类名
static = {}, -- 静态函数表,包括一些关键字比如new,在middleclass中是用静态函数来模拟
}

setmetatable(aClass, {
__tostring = _tostring, -- 设置aClass的元方法__tostring
__index = aClass.static, -- class的索引表设为其static表,如此class对象可以访问其static表中的函数
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
-- 还要排除static表,因为不能简单的记录static引用,这会覆盖aClass现有的static表
for name,method in pairs(mixin) do
if name ~= 'included' and name ~= 'static' then
aClass[name] = method
end
end

-- 把mixin.static表内容添加到aClass.static中
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

local DefaultMixin = {
--初始化
initialize = function(instance, ...) end,

static = {
--创建实例
allocate = function(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return { class = aClass, initialize = aClass.initialize }
end,

--创建实例并初始化
new = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end,
},
}

function middleclass.class(name)
return _includeMixin(_createClass(name), DefaultMixin)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass

__instanceDict表

middleclass把新定义的函数,MyClass.func()或者MyClass:func(),都通过__newindex元函数记录在class.__instanceDict表中,然后在该class创建实例时,把__instanceDict设为实例的metatable以及元表的__index的值

  • 在middlecalss眼中,MyClass.func()或者MyClass:func()都是middleclass机制中的成员函数
  • 在我们的应用层面,MyClass.func()像静态函数一样使用,MyClass:func()像成员函数一样使用
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
---middleclass---
local middleclass = {}

local function _propagateInstanceMethod(aClass, name, f)
-- 新定义的函数将被添加为成员函数
aClass.__instanceDict[name] = f
end

local function _declareInstanceMethod(aClass, name, f)
_propagateInstanceMethod(aClass, name, f)
end

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _createClass(name)
local dict = {}
dict.__index = dict

local aClass = {
name = name, -- 类名
static = {}, -- 静态函数表,包括一些关键字比如new,在middleclass中是用静态函数来模拟
__instanceDict = dict, -- 用作设为实例的metatable以及metatable的__index的值
}

-- aClass.static的__index行为: 从aClass.__instanceDict进行rawget
-- 其实__instanceDict没有自己的metatable,从而没有__index表,任何对__instanceDict的get都是rawget
setmetatable(aClass.static, {__index = function(_, k)
return rawget(dict, k)
end})

setmetatable(aClass, {
__tostring = _tostring, -- 设置aClass的元方法__tostring
__index = aClass.static, -- class的索引表设为其static表,如此class对象可以访问其static中的函数以及__instanceDict中的raw函数
__newindex = _declareInstanceMethod,
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
-- 还要排除static表,因为不能简单的记录static引用,这会覆盖aClass现有的static表
for name,method in pairs(mixin) do
if name ~= 'included' and name ~= 'static' then
aClass[name] = method -- 记住现在这个操作会触发_declareInstanceMethod函数
end
end

-- 把mixin.static表内容添加到aClass.static中
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

local DefaultMixin = {
-- 预留虚函数: 初始化,可以当做构造函数来用
initialize = function(instance, ...) end,

static = {
-- 创建实例
allocate = function(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = aClass }, aClass.__instanceDict) -- class的__instanceDict表用作设为实例的metatable以及metatable的__index的值
end,

-- 创建实例并初始化
new = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end,
},
}

function middleclass.class(name)
return _includeMixin(_createClass(name), DefaultMixin)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass

一些细节

  • 可以用 ClassA()的写法来new一个实例
  • 实例对象的__tostring元函数
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
---middleclass---
local middleclass = {}

local function _propagateInstanceMethod(aClass, name, f)
-- 新定义的函数将被添加为成员函数
aClass.__instanceDict[name] = f
end

local function _declareInstanceMethod(aClass, name, f)
_propagateInstanceMethod(aClass, name, f)
end

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _call(aClass, ...)
return aClass:new(...)
end

local function _createClass(name)
local dict = {}
dict.__index = dict

local aClass = {
name = name, -- 类名
static = {}, -- 静态函数表,包括一些关键字比如new,在middleclass中是用静态函数来模拟
__instanceDict = dict, -- 用作设为实例的metatable以及metatable的__index的值
}

-- aClass.static的__index行为: 从aClass.__instanceDict进行rawget
-- 其实__instanceDict没有自己的metatable,从而没有__index表,任何对__instanceDict的get都是rawget
setmetatable(aClass.static, {__index = function(_, k)
return rawget(dict, k)
end})

setmetatable(aClass, {
__tostring = _tostring, -- 设置aClass的元方法__tostring
__index = aClass.static, -- class的索引表设为其static表,如此class对象可以访问其static中的函数以及__instanceDict中的raw函数
__newindex = _declareInstanceMethod,
__call = _call, -- 让我们可以用 ClassA()的写法来创建实例
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
-- 还要排除static表,因为不能简单的记录static引用,这会覆盖aClass现有的static表
for name,method in pairs(mixin) do
if name ~= 'included' and name ~= 'static' then
aClass[name] = method -- 记住现在这个操作会触发_declareInstanceMethod函数
end
end

-- 把mixin.static表内容添加到aClass.static中
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

local DefaultMixin = {
-- 没有记录在static中的函数将在class导入DefaultMixin的时候
-- 通过aClass[name] = func这样的语句触发 _declareInstanceMethod
-- 从而记录到aClass.__instanceDict中
-- 由于aClass.__instanceDict同时还是实例对象的metatable,所以下面的__tostring也将成为实例对象的元函数
__tostring = function(instance)
return "instance of " .. tostring(instance.class)
end,

-- 预留虚函数: 初始化,可以当做构造函数来用。所有实例共用一个函数对象即可。
initialize = function(instance, ...) end,

static = {
-- 创建实例
allocate = function(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = aClass }, aClass.__instanceDict) -- class的__instanceDict表用作设为实例的metatable以及metatable的__index的值
end,

-- 创建实例并初始化
new = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end,
},
}

function middleclass.class(name)
return _includeMixin(_createClass(name), DefaultMixin)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass
1
2
3
4
5
6
7
8
9
10
---Animal---
local Class = require 'middleclass'

local Animal = Class('Animal')

function Animal:initialize(name)
print('Animal initialize name:'.. name)
end

return Animal

测试:

1
2
3
4
5
6
7
8
---test---
local Animal = require 'Animal'

local dog = Animal('dog') -- Animal initialize name:dog

print(dog) -- instance of class Animal

return dog

继承

实现【继承】关键字

和new一样,middleclass用静态函数subclass来模拟实现extends关键字

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
---middleclass---
local middleclass = {}

local function _propagateInstanceMethod(aClass, name, f)
-- 创建子类的时候,父类中__instanceDict的__index会把子类中__instanceDict的__inex覆盖掉,这种情况需要过滤掉
if name == '__index' then return end

-- 新定义的函数将被添加为成员函数
aClass.__instanceDict[name] = f
end

local function _declareInstanceMethod(aClass, name, f)
_propagateInstanceMethod(aClass, name, f)
end

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _call(aClass, ...)
return aClass:new(...)
end

local function _createClass(name, super)
local dict = {}
dict.__index = dict

local aClass = {
name = name, -- 类名
super = super, -- 父类
static = {}, -- 静态函数表,包括一些关键字比如new,在middleclass中是用静态函数来模拟
__instanceDict = dict, -- 用作设为实例的metatable以及metatable的__index的值
subclasses = setmetatable({}, {__mode='k'}) -- 子类表(弱键引用),key是类,value为布尔
}

-- aClass.static的__index行为: 从aClass.__instanceDict进行rawget
-- 其实__instanceDict没有自己的metatable,从而没有__index表,任何对__instanceDict的get都是rawget
-- 如果有父类的话,还会尝试去父类(进而沿着继承链上所有类定义)的static/__instanceDict 中查询
if super then
setmetatable(aClass.static, {__index = function(_, k)
return rawget(dict, k) or super.static[k]
end})
else
setmetatable(aClass.static, {__index = function(_, k)
return rawget(dict, k)
end})
end

setmetatable(aClass, {
__tostring = _tostring, -- 设置aClass的元方法__tostring
__index = aClass.static, -- class的索引表设为其static表,如此class对象可以访问其static中的函数以及__instanceDict中的raw函数
__newindex = _declareInstanceMethod,
__call = _call, -- 让我们可以用 ClassA()的写法来创建实例
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
-- 还要排除static表,因为不能简单的记录static引用,这会覆盖aClass现有的static表
for name,method in pairs(mixin) do
if name ~= 'included' and name ~= 'static' then
aClass[name] = method -- 记住现在这个操作会触发_declareInstanceMethod函数
end
end

-- 把mixin.static表内容添加到aClass.static中
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

local DefaultMixin = {
-- 没有记录在static中的函数将在class导入DefaultMixin的时候
-- 通过aClass[name] = func这样的语句触发 _declareInstanceMethod
-- 从而记录到aClass.__instanceDict中
-- 由于aClass.__instanceDict同时还是实例对象的metatable,所以下面的__tostring也将成为实例对象的元函数
__tostring = function(instance)
return "instance of " .. tostring(instance.class)
end,

-- 预留虚函数: 初始化,可以当做构造函数来用。所有实例共用一个函数对象即可。
initialize = function(instance, ...) end,

static = {
-- 创建实例
allocate = function(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = aClass }, aClass.__instanceDict) -- class的__instanceDict表用作设为实例的metatable以及metatable的__index的值
end,

-- 创建实例并初始化
new = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end,

subclass = function(aClass, name)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")

local subclass = _createClass(name, aClass)

-- 创建子类只继承父类的成员函数,不应继承父类的静态函数
for name,method in pairs(aClass.__instanceDict) do
_propagateInstanceMethod(subclass, name, method)
end

-- 为子类定义一个缺省的initialize函数,默认调用其父类的initialize函数
subclass.initialize = function(instance, ...)
aClass.initialize(instance, ...)
end

aClass.subclasses[subclass] = true -- 标记父类已拥有该子类
aClass:subclassed(subclass) -- 回调:创建子类完毕

return subclass
end,

-- 缺省实现 创建子类完毕回调
subclassed = function(aClass, other) end,

-- 判断是否指定类的子类
isSubclassOf = function(aClass, other)
return type(other) == 'table' and
type(aClass.super) == 'table' and
( aClass.super == other or aClass.super:isSubclassOf(other) )
end,

-- 批量include
include = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do
_includeMixin(aClass, mixin)
end
return aClass
end
},
}

function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---Animal---
local Class = require 'middleclass'

local Animal = Class('Animal')

function Animal:initialize(name)
self.instance_name = name
end

function Animal:get_name1()
return self.instance_name
end

function Animal:move()
print('Animal:move')
end

function Animal:eat()
print('Animal:eat!')
end

return Animal
1
2
3
4
5
6
7
8
9
10
11
---Dog---
local Class = require 'middleclass'
local Animal = require 'Animal'

local Dog = Class('Dog', Animal)

function Dog:move()
print('Dog:move walking!')
end

return Dog

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
---test---
local Animal = require 'Animal'
local Dog = require 'Dog'

print(Dog:isSubclassOf(Animal)) -- true

local dog = Dog('hashiqi')

print('dog_name:'..dog:get_name1()) -- dog_name:hashiqi
dog:eat() -- Animal:eat!
dog:move() -- Dog:move walking!

return dog

继承成员函数

middleclass的做法是把super中的所有成员函数在每个子类中都复制一份

  1. 创建subclass时,会调用_propagateInstanceMethod把当时super中所有成员函数复制到subclass中
  2. super声明新函数时,会把该新函数在super所有子类中的__instanceDict表中也复制一份

    实际应用中大部分都是先完整的声明完super,再去创建其subclass。 否则的话,super声明新函数时,会先通过subclass中的\__declaredMethods判断subclass是否声明过该函数 若没有声明,将这个函数复制到subclass中的\__instanceDict表中,否则不做处理。

__declaredMethods表中记录的是通过声明创建的函数,__instanceDict表中记录了所有的函数:声明得到的,以及从所有父类那里复制得到的

基于上述做法,每个子类中的__instanceDict都包含了其所有父类的__instanceDict的集合。即子类拥有父类函数。
如果子类重新定义了一个同名函数,那么将覆盖掉父类函数。
子类可以通过super关键字来调用函数的父类版本

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
---middleclass---
local middleclass = {}

-- 整合用户指定的__index函数
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]

if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end

local function _propagateInstanceMethod(aClass, name, f)
-- 创建子类的时候,父类中__instanceDict的__index会把子类中__instanceDict的__inex覆盖掉
-- 当用户尝试声明__index函数时,需要做一些处理:保留class.__instanceDict作为实例的__index表,整合用户指定的__index函数/表
f = name == "__index" and _createIndexWrapper(aClass, f) or f

-- 新定义的函数将被添加为成员函数
aClass.__instanceDict[name] = f

-- 将所有没有声明该函数的(即__declaredMethods表没有该函数)子类的__instanceDict表中记录该函数
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end

local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f -- 记录到__declaredMethods表

-- 揭示了middleclass的一个小技巧。当执行 ClassA.func = nil代码时
-- 会尝试从父类获取同名成员函数来用
if f==nil and aClass.super then
f = aClass.super.__instanceDict[name]
end

_propagateInstanceMethod(aClass, name, f)
end

local function _tostring(aClass)
return 'class '..aClass.name
end

local function _call(aClass, ...)
return aClass:new(...)
end

local function _createClass(name, super)
local dict = {}
dict.__index = dict

local aClass = {
name = name, -- 类名
super = super, -- 父类
static = {}, -- 静态函数表,包括一些关键字比如new,在middleclass中是用静态函数来模拟
__instanceDict = dict, -- 用作设为实例的metatable以及metatable的__index的值
subclasses = setmetatable({}, {__mode='k'}), -- 子类表(弱键引用),key是类,value为布尔
__declaredMethods = {}, -- 用来记录在这个类中声明(而不是继承自父类)的函数
}

-- aClass.static的__index行为: 从aClass.__instanceDict进行rawget
-- 其实__instanceDict没有自己的metatable,从而没有__index表,任何对__instanceDict的get都是rawget
-- 如果有父类的话,还会尝试去父类(进而沿着继承链上所有类定义)的static/__instanceDict 中查询
if super then
setmetatable(aClass.static, {__index = function(_, k)
return rawget(dict, k) or super.static[k]
end})
else
setmetatable(aClass.static, {__index = function(_, k)
return rawget(dict, k)
end})
end

setmetatable(aClass, {
__tostring = _tostring, -- 设置aClass的元方法__tostring
__index = aClass.static, -- class的索引表设为其static表,如此class对象可以访问其static中的函数以及__instanceDict中的raw函数
__newindex = _declareInstanceMethod,
__call = _call, -- 让我们可以用 ClassA()的写法来创建实例
})

return aClass
end

-- 导入mixin
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', 'mixin must be a table.')

-- 将mixin的所有值复制到aClass中
-- 除了included,这个名字默认是保留给mixin的回调
-- 还要排除static表,因为不能简单的记录static引用,这会覆盖aClass现有的static表
for name,method in pairs(mixin) do
if name ~= 'included' and name ~= 'static' then
aClass[name] = method -- 记住现在这个操作会触发_declareInstanceMethod函数
end
end

-- 把mixin.static表内容添加到aClass.static中
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end

-- 执行mixin included回调
if type(mixin.included) == 'function' then
mixin:included(aClass)
end

return aClass
end

local DefaultMixin = {
-- 没有记录在static中的函数将在class导入DefaultMixin的时候
-- 通过aClass[name] = func这样的语句触发 _declareInstanceMethod
-- 从而记录到aClass.__instanceDict中
-- 由于aClass.__instanceDict同时还是实例对象的metatable,所以下面的__tostring也将成为实例对象的元函数
__tostring = function(instance)
return "instance of " .. tostring(instance.class)
end,

-- 预留虚函数: 初始化,可以当做构造函数来用。所有实例共用一个函数对象即可。
initialize = function(instance, ...) end,

isInstanceOf = function(instance, aClass)
return type(aClass) == 'table' and
type(instance) == 'table' and
type(instance.class) == 'table' and
(
instance.class == aClass or
type(instance.class.isSubclassOf) == 'function' and instance.class:isSubclassOf(aClass)
)
end,

static = {
-- 创建实例
allocate = function(aClass)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = aClass }, aClass.__instanceDict) -- class的__instanceDict表用作设为实例的metatable以及metatable的__index的值
end,

-- 创建实例并初始化
new = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = aClass:allocate()
instance:initialize(...)
return instance
end,

subclass = function(aClass, name)
assert(type(aClass) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")

local subclass = _createClass(name, aClass)

-- 创建子类只继承父类的成员函数,不应继承父类的静态函数
for name,method in pairs(aClass.__instanceDict) do
_propagateInstanceMethod(subclass, name, method)
end

-- 为子类定义一个缺省的initialize函数,默认调用其父类的initialize函数
subclass.initialize = function(instance, ...)
aClass.initialize(instance, ...)
end

aClass.subclasses[subclass] = true -- 标记父类已拥有该子类
aClass:subclassed(subclass) -- 回调:创建子类完毕

return subclass
end,

-- 缺省实现 创建子类完毕回调
subclassed = function(aClass, other) end,

-- 判断是否指定类的子类
isSubclassOf = function(aClass, other)
return type(other) == 'table' and
type(aClass.super) == 'table' and
( aClass.super == other or aClass.super:isSubclassOf(other) )
end,

-- 批量include
include = function(aClass, ...)
assert(type(aClass) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do
_includeMixin(aClass, mixin)
end
return aClass
end
},
}

function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end

-- 模拟oop class关键字的风格
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end} )

return middleclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---Animal---
local Class = require 'middleclass'

local Animal = Class('Animal')

function Animal:initialize(name)
self.instance_name = name
end

function Animal:get_name1()
return self.instance_name
end

function Animal:move()
print('Animal:move')
end

function Animal:eat()
print('Animal:eat!')
end

return Animal
1
2
3
4
5
6
7
8
9
10
11
---Dog---
local Class = require 'middleclass'
local Animal = require 'Animal'

local Dog = Class('Dog', Animal)

function Dog:move()
print('Dog:move walking!')
end

return Dog

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---test---
local Animal = require 'Animal'
local Dog = require 'Dog'

print(Dog:isSubclassOf(Animal)) -- true

local dog = Dog('hashiqi')

print('dog_name:'..dog:get_name1()) -- dog_name:hashiqi
dog:eat() -- Animal:eat!
dog:move() -- Dog:move walking!

print(dog:isInstanceOf(Dog)) -- true
print(dog:isInstanceOf(Animal)) -- true

return dog