1. 创建类
local class = require "middleclass"
local parent = class("parent")
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
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
class调用通过middleclass的__call元方法调用到middleclass.class。在class函数中有两个不同的处理情况,如果传入父类,根据父类生成类模板,否则生成一个基类模板。
1.1 无基类情况
1.1.1 _createClass
无论是否有基类,首先都会通过_createClass函数创建类的基本模板:
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
函数首先创建了类模板aClass,包含六个字段
名称 | 含义 |
---|---|
name | 类名称 |
super | 基类模板 |
static | 类静态函数(包含allocate、new、subclass、subclassed、isSubclassOf、include) |
__instanceDict | 实例属性table(包含实例化出的对象拥有的函数,字段等),该table的__index字段指向自己 |
__declaredMethods | 类自身声明的属性,不包括继承来的 |
subclasses | 子类列表,key为弱引用 |
之后设置aClass的static字段的原表,当触发原表索引时,无论是否有父类,首先在aClass的__instanceDict中查找目标,如果没有找到且有父类,则会继续在父类的static中继续查找,所有的上表里列出的static函数只有在最上层基类的static字段中存储,最主要的使用就是调用new函数,不过从代码上看是可以通过类模板直接调用成员函数
local child = require "child"
child:func1()
最后设置aClass的元表,元表的__index指向aClass的static方法,因此通过类模板调用函数可以通过上述路径进行查找;__call函数设置为一个调用new方法的函数,因此可以不使用new进行实例化:
local function _call(self, ...) return self:new(...) end
local child = require "child"
local c1 = child() -- 等同于 child:new()
__newindex指向_declareInstanceMethod函数,该函数会将该类声明时的函数或字段添加到aClass的__declaredMethods字段中,并通过_propagateInstanceMethod函数将该函数或字段写入aClass的__instanceDict字段并通过递归调用_propagateInstanceMethod函数传递给所有子类
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
在_propagateInstanceMethod中对子类进行传播时会先判断aClass的__declaredMethods是否已经有同名的函数或字段,如果有则不覆盖,这样就达到了子类同名函数或字段覆盖父类值的目的
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
从上面的代码可以看出,aClass中添加的所有新函数或字段都会存储到__instanceDict中。
1.1.2 _includeMixin & DefaultMixin
在类模板创建完成之后通过class函数通过调用_includeMixin函数将DefaultMixin中的字段拷贝到aClass对应字段里。首先先看DefaultMixin的内容:
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
initialize = function(self, ...) end,
isInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.isSubclassOf) == 'function'
and self.class:isSubclassOf(aClass))
end,
static = {
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
subclass = function(self, name)
assert(type(self) == '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, self)
for methodName, f in pairs(self.__instanceDict) do
if not (methodName == "__index" and type(f) == "table") then
_propagateInstanceMethod(subclass, methodName, f)
end
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
subclassed = function(self, other) end,
isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:isSubclassOf(other) )
end,
include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
DefaultMixin是一个table,包含了三个实例函数和一个静态函数列表。_includeMixin 函数主要会将这些不同的函数分别拷贝的aClass的不同字段里
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end
首先对于key不为"included"(或者"static"的值,会直接插入aClass中,由于aClass的__newindex指向_declareInstanceMethod函数,因此DefaultMixin中定义的非static函数都会被最终设置到aClass的__instanceDict中。static函数会被设置到aClass的static字段中。included没有找到相关的声明,不懂这个是啥。。。。
总结一下,在没有基类时,创建一个类模板,会首先创建一个aClass的table,包含名称、基类、字段、子类等信息,并设置该table的元表,元表的__index指向aClass的static字段,可以使用类模板调用静态函数;__call指向一个调用new的函数,可以直接通过类模板创建实例对象;__newindex指向一个函数,该函数把新增函数或字段都填入__instanceDict和__declaredMethods中。
通过断点看一下具体的内容:
当调用完成class方法,parent类应该只包含默认的信息:
再往下走一步,添加member1之后,可以看到__instanceDict和__declaredMethods中都加入了member1的数值
1.2 有基类情况
有基类的情况会调用基类的subclass函数
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
前文提到subclass是DefaultMixin中定义的一个static函数,因此通过类模板进行调用。函数首先通过_createClass创建一个模板,之后通过_propagateInstanceMethod将模板的__instanceDict除了table类型的__index之外的字段复制给子类模板,之后将子类的初始化函数设置为调用父类初始化函数的封装,并将子类模板注册给父类模板的subclasses字段中,并调用父类的subclassed静态方法。
subclass = function(self, name)
assert(type(self) == '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, self)
for methodName, f in pairs(self.__instanceDict) do
if not (methodName == "__index" and type(f) == "table") then
_propagateInstanceMethod(subclass, methodName, f)
end
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
这里有个问题,父类向子类拷贝对象时是浅拷贝,因此像member2这种字段如果像测试代码中这么声明会导致所有的实例化对象引用的是同一个值
local child = require "child"
local c1 = child()
local c2 = child()
c1.member2.value = 30
c2.member2.value = 40
print(c1.member2.value, c2.member2.value)
上面代码的输出为
40 40
使用时要注意这个问题,可以在initialize函数中创建避免这个问题:
function parent:initialize()
self.member3 = {
value = 1
}
print("parent initialize")
end
2 实例化对象
根据前文所述,在调用new函数时,会调用到最顶层基类static中的new方法,也就是DefaultMixin中定义的new方法
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
实例化的对象中只有一个class指向类模板,并将元表设置为类模板的__instanceDict字段,在用实例调用函数或者访问字段时访问的是__instanceDict对应的属性。
3 其他函数说明
3.1 isInstanceOf
该函数用来判断某个实例的类型是否是制定类模板,从函数实现上看也会判断实例的类型是否是继承自制定类模板
isInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.isSubclassOf) == 'function'
and self.class:isSubclassOf(aClass))
end,
isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:isSubclassOf(other) )
end,
3.2 _createIndexWrapper
对类模板自定义的__index进行封装,没有用过,希望有大佬能帮说明一下
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
elseif type(f) == "function" then
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
else
return (f(self, name))
end
end
else -- if type(f) == "table" then
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
else
return f[name]
end
end
end
end
3.3 include
DefaultMixin中定义的static函数,可以把其他table中的内容插入到类声明中
-- staticfunc.lua
return {
extraFunc = function ()
print("extraFunc")
end,
static = {
extraStaticFunc = function ()
print("extraStaticFunc")
end,
}
}
local class = require "middleclass"
local staticfunc = require "staticfunc"
local parent = class("parent") : include(staticfunc)
local child = require "child"
local c1 = child()
c1:extraFunc()
child:extraStaticFunc()
测试代码
-- parent.lua
local class = require "middleclass"
local staticfunc = require "staticfunc"
local parent = class("parent") : include(staticfunc)
parent.member1 = 1
parent.member2 = {
value = 1
}
function parent:initialize()
self.member3 = {
value = 1
}
print("parent initialize")
end
function parent:func1()
print("parent func1")
end
return parent
-- child.lua
local class = require "middleclass"
local parent = require "parent"
local child = class("child", parent)
function child:initialize()
child.super.initialize(self)
print("child initialize")
end
return child